SpringBoot+shiro的平台构建以及需要注意的地方(一)

337 阅读8分钟

描述:

在工作中,其实用到权限认证的环境还挺多的,就想着做个小例子记录一把,留着以后复习。那就直奔主题了。

环境构建

1.创建SpringBoot项目

常规操作

在这里插入图片描述

目录结构如下

此为controller层,以及各种配置。Swagger大家不要管,只是为前端提供api查看以及测试。

在这里插入图片描述
实体层
在这里插入图片描述
业务层biz处理业务逻辑。
在这里插入图片描述
在本项目的pom文件中导入:

<!--shiro管理控制-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>

web层需要用到biz层,biz层需要用实体层那么就引入,以web层为例:

这样web层的pom文件引入后就可以访问service层的接口了

 <dependencies>
        <dependency>
            <groupId>com.zk</groupId>
            <artifactId>zk-biz</artifactId>
            <version>0.0.1-SNAPSHOT</version>
         </dependency>
    </dependencies>

2.构建实体类以及用户角色权限枚举类

底下也都是Model层内容:

构建出自己的pojo,一个POJOBase为所以对象基类,用到uid所以这里只截uid

在这里插入图片描述
在这里插入图片描述
用户权限枚举类,此权限直接是包含关系,你可以这样理解牛逼用户可以有普通用户的权限。

/**
 * @Classname UserRole
 * @Description 用户权限
 * @Date 2019/12/30 10:14
 * @Created by ZhangKai
 */
public enum UserRole {
    //默认用户权限
//    普通用户
    LEVE_0("0","普通用户"),
    //牛逼用户
    LEVE_1("1","牛逼用户"),
    //未知权限
    LEVE_999("999","未知权限");
    private String code;
    private String name;
     UserRole(String code,String name){
        this.code = code;
        this.name =name;
    }

    public String getCode() {
        return code;
    }
    public String getName() {
        return name;
    }

    public static UserRole getEnumType(String key) {
        UserRole[] alarmGrades = UserRole.values();
        for (int i = 0; i < alarmGrades.length; i++) {
            if (alarmGrades[i].getCode().equals(key)) {
                return alarmGrades[i];
            }
        }
        return UserRole.LEVE_999;
    }

    /**
     * 根据Key得到枚举的Value
     * Lambda表达式,比较判断(JDK 1.8)
     *
     * @param key
     * @return
     */
    public static UserRole getUserRole(String key) {
        UserRole[] alarmGrades = UserRole.values();
        UserRole result = Arrays.asList(alarmGrades).stream()
                .filter(alarmGrade -> alarmGrade.getCode().equals(key))
                .findFirst().orElse(UserRole.LEVE_999);
        return result;
    }
}

/**
 * @Classname UserState
 * @Description 用户状态
 * @Date 2019/12/30 11:00
 * @Created by ZhangKai
 */
public enum UserState {
    //默认用户权限
//    普通用户
    STATE_0("0","未登录"),
    //牛逼用户
    STATE_1("1","登录中"),
    //未知权限
    STATE_999("999","未知情况");
    private String code;
    private String name;
    UserState(String code,String name){
        this.code = code;
        this.name =name;
    }

    public String getCode() {
        return code;
    }
    public String getName() {
        return name;
    }

    public static UserState getEnumType(String key) {
        UserState[] alarmGrades = UserState.values();
        for (int i = 0; i < alarmGrades.length; i++) {
            if (alarmGrades[i].getCode().equals(key)) {
                return alarmGrades[i];
            }
        }
        return UserState.STATE_999;
    }

    /**
     * 根据Key得到枚举的Value
     * Lambda表达式,比较判断(JDK 1.8)
     *
     * @param key
     * @return
     */
    public static UserState getUserStateRole(String key) {
        UserState[] alarmGrades = UserState.values();
        UserState result = Arrays.asList(alarmGrades).stream()
                .filter(alarmGrade -> alarmGrade.getCode().equals(key))
                .findFirst().orElse(UserState.STATE_999);
        return result;
    }
}

3.重要的一步,extends AuthorizingRealm实现它的两个方法。

(1)简单写个service实现模拟返回数据库值

/**
 * @Classname UserServiceImpl
 * @Description TODO
 * @Date 2019/12/27 16:05
 * @Created by ZhangKai
 */
@Service
public class UserServiceImpl implements UserService {
   Logger logger=LoggerFactory.getLogger(UserServiceImpl.class.getName());
   
    //根据id判断是否存在此用户,用于查询权限状态
   @Override
   public User selectRoleByUser(String userId) {
      User user=new User();
      if("007".equals(userId)) {
         user.setuId("007");
         user.setuName("zhangsan");
         user.setuPassword("123");
         user.setuStatus(UserState.STATE_0.getCode());
         user.setuLeveL(UserRole.LEVE_0.getCode());
         user.setuSex(true);
         user.setuWork("公职");
      }
      return user;
   }
    //根据账号为此用户做登录校验,密码校验在下方
   @Override
   public User selectRoleByName(String username) {
      User user = new User();
      if("zhangsan".equals(username)) {
         user.setuId("007");
         user.setuName("zhangsan");
         user.setuPassword("123");
         user.setuStatus(UserState.STATE_0.getCode());
         user.setuLeveL(UserRole.LEVE_0.getCode());
         user.setuSex(true);
         user.setuWork("公职");
      }
      return user;
   }
}

(2)权限认证以及登录校验(重要):

主要是biz层的内容:

此类需要多多注意的是此注入不是平常的@Autowired注解

@Resource private UserService userService;

/**
 * @Classname ShiroRealm
 * @Description TODO
 * @Date 2019/12/27 17:01
 * @Created by ZhangKai
 */
public class ShiroRealm extends AuthorizingRealm {
    private static Logger logger = LoggerFactory.getLogger(ShiroRealm.class);
   //注意此处 用的不是@Autowired注解,因为此层并非业务逻辑层,自动注入会报错
    @Resource
    private UserService userService;

    /**
     * 授权逻辑,根据用户信息,存入此用户所拥有的权限。判断权限校验不在此
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) throws AuthenticationException {
        logger.info("--------   权限配置   -------");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        POJOBase userId = (POJOBase) principals.getPrimaryPrincipal();
        try {
            //注入角色(查询所有的角色注入控制器)
            logger.info("-------- 所传用户:"+userId.getuId()+"去查询该用户所拥有的权限");
            //如果此用户存在
            if (!EmptyObject.isEmpty(userId.getuId()))
            {
                User user=userService.selectRoleByUser(userId.getuId());
                //获取此用户权限(正常权限需要判断是否有包含关系,如果没有就是集合)
//                此用户角色
                authorizationInfo.addRole(user.getuLeveL());
                //注入角色所有权限(查询用户所有的权限注入控制器)
                //权限名称
                authorizationInfo.addRole(UserRole.getUserRole(user.getuLeveL()).getName());
                logger.info("查到该用户权限为"+user.getuLeveL()+":"+UserRole.getUserRole(user.getuLeveL()).getName());
            }else {
                logger.info("--------   用户不存在   -------");
                throw new UnknownAccountException("不存在此用户");        // 没有找到账号
            }
        }catch (Exception e)
        {
            logger.info(e.getMessage());
        }
        return authorizationInfo;
    }

    /**
     * 用户登录认证逻辑,登录状态验证 1
     * 当调用Subject currentUser = SecurityUtils.getSubject();
     * currentUser.login(token);
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户的输入的账号密码
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法,目前不做额外缓存
        User user = userService.selectRoleByName(username);
        if(null == user){
            logger.info("--------   用户不存在   -------");
            throw new UnknownAccountException("不存在此用户");
        }else {
            if(password.equals(user.getuPassword())){
                logger.info("===   密码正确 ===");
                if(UserState.STATE_1.getCode().equals(user.getuStatus())){
                    logger.info("===   已登录 ===");
                    throw new LockedAccountException("已经登录请不要从新登录");
                }else if (UserState.STATE_0.getCode().equals(user.getuStatus())){
                    logger.info("===   登录通过 ===");
                    SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(
                            user,user.getuPassword().toCharArray(),getName());
                    return authorizationInfo;
                }else {
                    logger.info("===   未知状态 ===");
                    throw new DisabledAccountException();
                }
            } else {
                logger.info("===   密码错误 ===");
                throw new IncorrectCredentialsException();
            }
        }
    }
}

4.创建controller并增加shiro配置(重点)

(1)用户处理主要的类,此处 权限校验使用的是 @RequiresRoles()注解,你也可以使用subject.hasRole() 或 subject.isPermitted()的方式在Controller的方法里去判断。

/**
 * @Classname UserController
 * @Description TODO
 * @Date 2019/12/27 16:07
 * @Created by ZhangKai
 */
@Controller
@RequestMapping("/user")
@Api(description = "用户管理")
public class UserController {

    private static Logger logger = LoggerFactory.getLogger(UserController.class);
    @RequestMapping("/login")
    @ApiOperation(value = "跳转用户页面" ,notes = "跳转用户页面")
    public String login(HttpServletRequest request)
    {
        logger.info("===   进入到登录页面   ===");
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(request.getParameter("uName"),request.getParameter("uPassword"));
        try {
            logger.info("===   开始登录校验   ===");
            subject.login(token);    //调用上面重载的方法doGetAuthenticationInfo
            logger.info("===   登录校验通过   ===");
        } catch (UnknownAccountException e){
            logger.info("===   用户名不存在   ===");
//            baseResponse.setMsg("用户名不存在");
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            logger.info("===   密码错误   ===");
          return "/errorPage.html";
        } catch (LockedAccountException e){
            logger.info("===   锁定的帐号   ===");
            return "/userLogin.html";
        }catch (DisabledAccountException e){
            logger.info("===   禁用的帐号   ===");
        } catch (Exception e){
            e.printStackTrace();
        }
        return "/";
    }
    @RequestMapping("/userRole1")
    @ApiOperation(value = "查看用户权限" ,notes = "查看用户权限")
    //拥有此角色的才可以访问
    @RequiresRoles("1")
    @ResponseBody
    public String userRole1()
    {
        logger.info("===   拥有权限:牛逼用户   ===");
        return "拥有牛逼用户权限";
    }

    @RequestMapping("/userRole0")
    @ApiOperation(value = "查看用户权限" ,notes = "查看用户权限")
    @ResponseBody
    //拥有此角色的才可以访问
    @RequiresRoles("0")
    public String userRole0()
    {
        logger.info("===   拥有权限:普通用户   ===");
        return "拥有普通用户权限";
    }

    @GetMapping("/logout")
    public String logout() {
        //使用权限管理工具进行用户的退出,跳出登录,给出提示信息
        SecurityUtils.getSubject().logout();
        logger.info("您已安全退出");
        return "redirect:/login";
    }
}

(2)创建shiro配置管理(重点)

biz层内创建shiro配置


/**
 * @Classname ShiroConfig
 * @Description TODO
 * @Date 2019/12/27 16:54
 * @Created by ZhangKai
 */

@Configuration
public class ShiroConfig {
    Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager security) {
        logger.info("-------------------- shiro filter -------------------");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(security);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //注意过滤器配置顺序 不能颠倒
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/*", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/configuration/**", "anon");
        //拦截其他所以接口
        filterChainDefinitionMap.put("/**", "authc");
        //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
        logger.info("===   设置默认页面为:/userPage/userLogin");
        shiroFilterFactoryBean.setLoginUrl("/userPage/userLogin");
        // 登录成功后要跳转的链接 自行处理。不用shiro进行跳转
        logger.info("===   如果登录成功跳转:/index");
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
        logger.info("===   如果未授权跳转:/user/unImpower");
        /*定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,需要继承FormAuthenticationFilter **本例中暂不自定义实现,在下一节实现验证码的例子中体现 */
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/unImpower");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * shiro 用户数据注入
     *
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        return shiroRealm;
    }

    /**
     * 配置管理层。即安全控制层
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        logger.info("注入Shiro的Web过滤器-->securityManager", ShiroFilterFactoryBean.class);
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }


    /**
     * 权限管理,不加入此方法,没有权限验证
     * shiro权限注解要生效,必须配置springAOP通过设置shiro的SecurityManager进行权限验证。
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 * @return
     *
     * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    /**
     * 处理未授权的异常,返回自定义的错误页面(403)
     * @return
     */
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        /*未授权处理页*/
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/userPage/unImpower");
        properties.setProperty("org.apache.shiro.authz.AuthorizationException", "/userPage/unImpower");
        resolver.setExceptionMappings(properties);
        return resolver;
    }
    
    /**
     * 开启shiro aop注解支持 使用代理方式所以需要开启代码支持
     * 一定要写入上面advisorAutoProxyCreator()自动代理。不然AOP注解不会生效
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     * Shiro生命周期处理器 * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}

当然,缓存配置主要也是在此处,目前先这样。然后启动调用看看吧。

调用http://localhost:8080/后发现进入到此页面,这也是上面配置的默认加载页面

在这里插入图片描述

<a href="/user/login?uName=zhangsan&uPassword=123">访问</a> 和上面serviceimpl的模拟返回值一样

点击访问后,运行 subject.login(token);然后调用 biz中ShiroRealm的doGetAuthenticationInfo方法
在这里插入图片描述

然后进入主页

在这里插入图片描述

点击牛逼权限

在这里插入图片描述

额对的, user.setuLeveL(UserRole.LEVE_0.getCode());给的是普通权限

然后试试普通的,点击普通权限。

在这里插入图片描述