springboot整合shiro+jwt+redis详解

Posted 码里法

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot整合shiro+jwt+redis详解相关的知识,希望对你有一定的参考价值。

原理

三大核心组件:Subject、SecurityManager、Realm

  • Subject
    主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
  • SecurityManager
    安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
  • Realm
    域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

    总结:
    应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
    我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

内部架构图:

整合

在springboot中整合shiro、redis和jwt,核心的配置:ShiroConfig、JwtFilter、ShiroRealm,其中jwt主要是负责生成token的工具,redis负责缓存token。
首先我们配置Realm,然后配置filter及jwt工具类,再用shiroConfig来将这些配置联系起来,组成完整的认证鉴权系统。

准备工作

springboot版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    <relativePath/>
</parent>

jwt和shiro版本

<!--JWT-->
     <dependency>
         <groupId>com.auth0</groupId>
         <artifactId>java-jwt</artifactId>
         <version>3.11.0</version>
     </dependency>
     <!--shiro-->
     <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring-boot-starter</artifactId>
         <version>1.7.1</version>
     </dependency>
     <dependency>
         <groupId>org.crazycake</groupId>
         <artifactId>shiro-redis</artifactId>
         <version>3.1.0</version>
         <exclusions>
             <exclusion>
                 <groupId>org.apache.shiro</groupId>
                 <artifactId>shiro-core</artifactId>
             </exclusion>
             <exclusion>
                 <groupId>com.puppycrawl.tools</groupId>
                 <artifactId>checkstyle</artifactId>
             </exclusion>
         </exclusions>
     </dependency>

各项配置

  • ShiroRealm
    主要负责认证(AuthenticationInfo)和鉴权(AuthorizationInfo)代码逻辑的实现。

    /**
    * 认证
    *
    * @author zwj
    */
    @Slf4j
    @Component
    public class ShiroRealm extends AuthorizingRealm 
    
       @Autowired
       private IUserService userService;
    
       @Autowired
       private StringRedisTemplate stringRedisTemplate;
    
       /**
        * 必须重写此方法,不然Shiro会报错
        */
       @Override
       public boolean supports(AuthenticationToken token) 
           return token instanceof JwtToken;
       
    
       /**
        * 授权(验证权限时调用)
        */
       @Override
       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
           SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
           return info;
       
    
       /**
        * 认证(登录时调用)
        */
       @Override
       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException 
           String accessToken = (String) token.getPrincipal();
           if (accessToken == null) 
               throw new AuthenticationException(CommonCode.WEB_TOKEN_NULL.getMessage());
           
           // 校验token有效性
           User tokenEntity = this.checkUserTokenIsEffect(accessToken);
           SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(tokenEntity, accessToken, getName());
           return info;
       
    
       /**
        * 校验token的有效性
        *  springboot2.3.+新增了一个配置项server.error.includeMessage,默认是NEVER,
        *  因此默认是不是输出message的,只要开启就可以了,否则无法拿到shiro抛出异常信息message
        * @param token
        */
       public User checkUserTokenIsEffect(String token) throws AuthenticationException 
           // 解密获得username,用于和数据库进行对比
           String userId = JwtUtil.getUserId(token);
           if (userId == null) 
               throw new AuthenticationException(CommonCode.WEB_TOKEN_ILLEGAL.getMessage());
           
    
           // 查询用户信息
           User loginUser = userService.getById(userId);
           if (loginUser == null) 
               throw new AuthenticationException(CommonCode.WEB_USER_NOT_EXIST.getMessage());
           
           // 判断用户状态
           if (loginUser.getStatus() != 0) 
               throw new LockedAccountException(CommonCode.WEB_ACCOUNT_LOCKED.getMessage());
           
           // 校验token是否超时失效 & 或者账号密码是否错误
           if (!jwtTokenRefresh(token, userId, loginUser.getUserPhone())) 
               throw new IncorrectCredentialsException(CommonCode.WEB_TOKEN_FAILURE.getMessage());
           
           return loginUser;
       
    
       /**
        * JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
        * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
        * 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
        * 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
        * 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
        * 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
        * 用户过期时间 = Jwt有效时间 * 2。
        *
        * @param userId
        * @param userPhone
        * @return
        */
       public boolean jwtTokenRefresh(String token, String userId, String userPhone) 
           //如果缓存中的token为空,直接返回失效异常
           String cacheToken = stringRedisTemplate.opsForValue().get(CommonConstant.PREFIX_USER_TOKEN + token);
           if (!StrUtils.isBlank(cacheToken)) 
               // 校验token有效性
               if (!JwtUtil.verify(cacheToken, userId, userPhone)) 
                    JwtUtil.sign(userId, userPhone);
               
               return true;
           
           return false;
       
    
       /**
        * 清除当前用户的权限认证缓存
        *
        * @param principals 权限信息
        */
       @Override
       public void clearCache(PrincipalCollection principals) 
           super.clearCache(principals);
       
    
    
  • JwtFilter
    这里会拦截需要认证和鉴权的请求,同时会捕获相应异常并抛出

    /**
     * 过滤器
     *
     * @author zwj
     */
    public class JwtFilter extends BasicHttpAuthenticationFilter 
    
        /**
         * 功能描述: 执行登录认证
         *
         * @param request
         * @param response
         * @param mappedValue
         * @return boolean
         * @author zhouwenjie
         * @date 2021/12/24 14:45
         */
        @SneakyThrows
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 
            try 
                executeLogin(request, response);
                return true;
             catch (Exception e) 
                throw new AuthenticationException(e.getMessage(), e);
            
        
    
        @Override
        protected boolean executeLogin(ServletRequest request, ServletResponse response) 
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String token = JwtUtil.getTokenByRequest(httpServletRequest);
            JwtToken jwtToken = new JwtToken(token);
            // 提交给realm进行登入,如果错误他会抛出异常并被捕获
            getSubject(request, response).login(jwtToken);
            // 如果没有抛出异常则代表登入成功,返回true
            return true;
        
    
        /**
         * 对跨域提供支持
         */
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception 
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
            // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
            if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) 
                httpServletResponse.setStatus(HttpStatus.OK.value());
                return false;
            
            return super.preHandle(request, response);
        
    
    
    

    JwtToken

    /**
     * token
     *
     * @author Mark sunlightcs@gmail.com
     */
    public class JwtToken implements AuthenticationToken 
        private static final long serialVersionUID = 1L;
        private String token;
    
        public JwtToken(String token)
            this.token = token;
        
    
        @Override
        public String getPrincipal() 
            return token;
        
    
        @Override
        public Object getCredentials() 
            return token;
        
    
    

    JwtUtil:token工具类

    
    /**
     * @Author zwj
     * @Desc JWT工具类
     **/
    public class JwtUtil 
    
        // Token过期时间180天(用户登录过期时间是此时间的两倍,以token在reids缓存时间为准)
        public static final long EXPIRE_TIME = 24 * 180 * 60 * 60 * 1000;
        public static final int days = 360;
        private static StringRedisTemplate stringRedisTemplate = SpringContextUtils.getBean(StringRedisTemplate.class);
    
        /**
         * 校验token是否正确
         *
         * @param token     密钥
         * @param userPhone 用户的密码
         * @return 是否正确
         */
        public static boolean verify(String token, String userId, String userPhone) 
            try 
                // 根据密码生成JWT效验器
                Algorithm algorithm = Algorithm.HMAC256(userPhone);
                JWTVerifier verifier = JWT.require(algorithm).withClaim("userId", userId).build();
                // 效验TOKEN
                verifier.verify(token);
                return true;
             catch (Exception exception) 
                return false;
            
        
    
        /**
         * 获得token中的信息无需secret解密也能获得
         *
         * @return token中包含的用户名
         */
        public static String getUserId(String token) 
            try 
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("userId").asString();
             catch (Exception e) 
                return null;
            
        
    
        /**
         * 生成签名,360天后过期
         *
         * @param userId    用户id
         * @param userPhone 用户的密码
         * @return 加密的token
         */
        public static String sign(String userId, String userPhone) 
    //        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(userPhone);
            // 附带userId信息  可以将user信息转成map存到这里
    //        String token = JWT.create().withClaim("userId", userId).withExpiresAt(date).sign(algorithm);
            String token = JWT.create().withClaim("userId", userId).sign(algorithm);
            stringRedisTemplate.opsForValue().set(CommonConstant.PREFIX_USER_TOKEN + token, token, days, TimeUnit.DAYS);
            return token;
    
        
    
        /**
         * 根据request中的token获取用户账号
         *
         * @param request
         * @return
         */
        public static String getUserIdByToken(HttpServletRequest request) 
            String accessToken = getTokenByRequest(request);
            String userId = getUserId(accessToken);
            return userId;
        
    
        /**
         * 获取 request 里传递的 token
         *
         * @param request
         * @return
         */
        public static String getTokenByRequest(HttpServletRequest request) 
            String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
            return token;
        
    
    

    过期时间根据自己需求设定。

  • ShiroConfig
    整合各项配置的联系,注意新版本和老版本的配置区别,新版本需要重新注入beanDefaultAdvisorAutoProxyCreator、AuthorizationAttributeSourceAdvisor,原因代码中也有详细注释。

    /**
     * Shiro配置
     *
     * @author zwj
     */
    @Configuration
    public class ShiroConfig 
    
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) 
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
    
    
            Map<String, String> filterMap = new LinkedHashMap<>();
            filterMap.put("/web/login/**", "anon");
            filterMap.put("/web/carOwner/list", "anon");
            filterMap.put("/web/passengers/list", "anon");
            filterMap.put("/web/user/sendSms", "anon");
            filterMap.put("/web/sysDictionary/queryByIds", "anon");
            filterMap.put("/web/user/addActive", "anon");
            filterMap.put("/web/sysNotice/list", "anon");
            filterMap.put("/web/sysAds/addViewNum", "anon");
            filterMap.put("/web/sysAds/list", "anon");
            filterMap.put("/web/sysAds/queryById", "anon");
            filterMap.put("/web/sysArea/list", "anon");
            //-------防止api文档被过滤掉
            filterMap.put("/doc.html", "anon");
            filterMap.put("/**/*.js", "anon");
            filterMap.put("/**/*.css", "anon");
            filterMap.put("/**/*.html", "anon");
            filterMap.put("/**/*.svg", "anon");
            filterMap.put("/**/*.pdf", "anon");
            filterMap.put("/**/*.jpg", "anon");
            filterMap.put("/**/*.png", "anon");
            filterMap.put("/**/*.ico", "anon");
            filterMap.put("/swagger-resources/**", "anon");
            filterMap.put("/v2/api-docs", "anon");
            filterMap.put("/v2/api-docs-ext", "anon");
            filterMap.put("/webjars/**", "anon");
            filterMap.put("/druid/**", "anon");
            filterMap.put("/", "anon");
            //=======防止api文档被过滤掉
            filterMap.put("/**", "jwt");
            //jwt过滤
            Map序言 
    

    本文讲解如何使用SpringBoot整合Shiro框架来实现认证及权限校验,但如今的互联网已经成为前后端分离的时代,所以本文在使用SpringBoot整合Shiro框架的时候会联合JWT一起搭配使用。

    Shiro

    Shiroapache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份
    认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

    Shiro架构图

    Shiro 核心组件

    用户、角色、权限之间的关系

    • 用户拥有不同角色
    • 角色拥有不同权限

    1、UsernamePasswordTokenShiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token
    2、SecurityManagerShiro 的核心部分,负责安全认证和授权。
    3、SujectShiro 的一个抽象概念,包含了用户信息。
    4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
    5、AuthenticationInfo,用户的角色信息集合,认证时使用。
    6、AuthorzationInfo,角色的权限信息集合,授权时使用。
    7、DefaultWebSecurityManager,安全管理器,开发者自定义的Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
    8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成。

    JWT

    JWT(JSON WEB TOKEN)JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。

    JWT的构成
    JWT由三部分构成:Header(头部)、Payload(载荷)和Signature(签名)。

    1.Header(头) 作用:记录令牌类型、签名算法等 例如:“alg":"HS256","type","JWT
    
    2.Payload(有效载荷)作用:携带一些用户信息 例如"userId":"1","username":"mayikt"
    
    3.Signature(签名)作用:防止Token被篡改、确保安全性 例如 计算出来的签名,一个字符串
    
    

    项目环境

    • Shiro:1.4.1
    • SpringBoot:2.5.6
    • JDK:1.8

    搭建项目

    pom依赖

    <dependencies>
    <!--    lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    <!--        aop-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    <!--        web-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    <!--        test-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    <!--        shiro-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring-boot-web-starter</artifactId>
                <version>1.4.1</version>
            </dependency>
    <!--        shiro-chcache-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>1.4.1</version>
            </dependency>
    <!--        jwt-->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.2.0</version>
            </dependency>
    <!--        fastjson-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.15</version>
            </dependency>
        </dependencies>
    

    JWTUtil

    public class JWTUtils 
    
        /**
         * 过期时间
         */
        private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;
    
        /**
         * 校验
         * @param token
         * @param username
         * @param password
         * @return
         */
        public static boolean verify(String token, String username, String password) 
            try 
                Algorithm algorithm = Algorithm.HMAC256(password);
                JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
                DecodedJWT jwt = verifier.verify(token);
                return true;
             catch (Exception e) 
                return false;
            
        
    
        /**
         * 颁发令牌
         * @param username
         * @param password
         * @return
         */
        public static String sign(String username, String password) 
            try 
                //设置过期时间:获取当前时间+过期时间(毫秒)
                Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
                //设置签名的加密算法:HMAC256
                Algorithm algorithm = Algorithm.HMAC256(password);
                // 附带username信息
                return JWT.create()
                        .withClaim("username", username)
                        .withExpiresAt(date)
                        .sign(algorithm);
             catch (UnsupportedEncodingException e) 
                return null;
            
        
    
        /**
         * 获取用户名
         * @param token
         * @return
         */
        public static String getUsername(String token) 
            if (token == null || "".equals(token)) 
                return null;
            
            try 
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("username").asString();
             catch (JWTDecodeException e) 
                return null;
            
        
    
    
    
    

    JWTToken

    JWTToken是定义的一个Token类,继承了AuthenticationToken类,实现getPrincipalgetCredentials方法,(这两个方法本来是用于获取token中的信息,和识别token的,但JWTUtils已经为我们提供了这样的方法,所以这两个方法对于JWTToken没有意义)。用于将客户端传来的Token进行封装,便于Realm识别Token类型,进行认证和授权。

    public class JWTToken implements AuthenticationToken 
    
        /**
         * 密钥
         */
        private String token;
    
        public JWTToken(String token) 
            this.token = token;
        
    
        @Override
        public Object getPrincipal() 
            return token;
        
    
        @Override
        public Object getCredentials() 
            return token;
        
    
    
    

    JWTFilter过滤器

    因为 JWT 的整合,我们需要⾃定义⾃⼰的过滤器 JWTFilterJWTFilter 继承了 BasicHttpAuthenticationFilter,并部分原⽅法进⾏了重写。

    public class JWTFilter extends BasicHttpAuthenticationFilter 
    
        /**
         * Header中的Token标志
         */
        private static String LOGIN_SIGN = "Authorization";
    
        /**
         * 是否允许访问
         * @param request
         * @param response
         * @param mappedValue
         * @return
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 
            if (isLoginAttempt(request, response)) 
                try 
                    executeLogin(request, response);
                 catch (Exception e) 
                    if (e instanceof AuthorizationException) 
                        throw new AuthorizationException("访问资源权限不足!");
                     else 
                        //token 异常 认证失败
                        throw new AuthenticationException("token 异常 认证失败");
                    
                
            
            return true;
        
    
        /**
         * 是登录尝试
         * @param request
         * @param response
         * @return
         */
        @Override
        protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) 
            HttpServletRequest req = (HttpServletRequest) request;
            //判断是否是登录请求
            String authorization = req.getHeader(LOGIN_SIGN);
            return authorization != null;
        
    
        /**
         * 执行登录
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        @Override
        protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception 
            HttpServletRequest req = (HttpServletRequest) request;
            String header = req.getHeader(LOGIN_SIGN);
            JWTToken token = new JWTToken(header);
            //提交给realm进⾏登⼊,如果错误他会抛出异常并被捕获
            getSubject(request, response).login(token);
    
            return true;
        
    
    

    自定义ShiroRealm

    自定义的Realm对象,该对象继承于AuthorizingRealm,实现了Shiro具体认证和授权的方法。

    • doGetAuthenticationInfo方法用于->认证:校验帐号和密码
    • doGetAuthorizationInfo方法用于->授权:授予角色和权限

    另外需要注意:
    必须要重写supports方法,因为是自己定义的Tokenshiro无法识别,需要修改Realm中的supports方法,使 shiro 支持自定义Token

    public class ShiroRealm extends AuthorizingRealm 
    
        @Autowired
        private RoleService roleService;
        @Autowired
        private MenuService menuService;
        @Autowired
        private UserService userService;
    
        /**
         * 因为是自己定义的Token,shiro无法识别,需要修改Realm中的supports方法,使 shiro 支持自定义token。
         * @param token
         * @return
         */
        @Override
        public boolean supports(AuthenticationToken token) 
            return token instanceof JWTToken;
        
    
        /**
         * 认证:校验帐号和密码
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException 
            String token = (String) authenticationToken.getCredentials();
            //从token中获取用户名
            String username = JWTUtils.getUsername(token);
            //获取数据库中存取的用户,密码是加密后的
            User user = userService.selectByUserName(username);
            if (user != null) 
                // 密码验证
                if (!JWTUtils.verify(token, username, user.getPassword())) 
                    // 密码不正确
                    throw new IncorrectCredentialsException();
                
                return new SimpleAuthenticationInfo(token, token, getName());
             else 
                throw new UnknownAccountException();
            
        
    
        /**
         * 授权:授予角色和权限
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
            //获取用户名
            String userName = JWTUtils.getUsername(principals.toString());
            //根据用户名查询用户
            User user = userService.selectByUserName(userName);
            //实例化一个授权信息
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            if (user != null) 
                //赋予角色
                List<Role> roles = roleService.selectRoleByUserId(user.getId());
                for (Role role : roles) 
                    //将角色添加到授权信息中
                    info.addRole(role.getRoleKey());
                
                //赋予资源
                List<Menu> permissions = menuService.selectPermsByUserId(user.getId());
                for (Menu permission : permissions) 
                    //将权限添加授权信息中
                    info.addStringPermission(permission.getPerms());
                
            
            return info;
        
    
    
    

    ShiroConfig

    ShiroConfig用于进行Shiro的相关配置,主要包括ShiroFilterFactoryBeanDefaultWebSecurityManagerRealm的配置。

    @Configuration
    public class ShiroConfig 
    
        /**
         * 生命周期处理器
         * @return
         */
        @Bean(name = "lifecycleBeanPostProcessor")
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() 
            return new LifecycleBeanPostProcessor();
        
    
        /**
         * 加密方式
         * @return
         */
        @Bean(name = "hashedCredentialsMatcher")
        public HashedCredentialsMatcher hashedCredentialsMatcher() 
            // 散列凭证匹配器
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            // 设置哈希算法名称,这里使用MD5算法
            credentialsMatcher.setHashAlgorithmName("MD5");
            // 设置哈希迭代,这里迭代2次,相当于 md5(md5(""))
            credentialsMatcher.setHashIterations(2);
            // 设置存储的凭据16进制编码,需要和生成密码时的一样,默认是 Base64
            credentialsMatcher.setStoredCredentialsHexEncoded(true);
            return credentialsMatcher;
        
    
        /**
         * 自定义Realm
         * @param cacheManager
         * @return
         */
        @Bean(name = "shiroRealm")
        @DependsOn("lifecycleBeanPostProcessor")
        public ShiroRealm shiroRealm(EhCacheManager cacheManager) 
            ShiroRealm realm = new ShiroRealm();
            realm.setCacheManager(cacheManager);
            return realm;
        
    
        /**
         * 缓存管理器
         * @return
         */
        @Bean(name = "ehCacheManager")
        @DependsOn("lifecycleBeanPostProcessor")
        public EhCacheManager ehCacheManager() 
            EhCacheManager ehCacheManager = new EhCacheManager();
            ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
            return ehCacheManager;
        
    
        /**
         * 安全管理器
         * @param shiroRealm
         * @return
         */
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm) 
            // 实例化会话管理器
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 设置缓存管理器
            securityManager.setCacheManager(ehCacheManager());
    
            /**
             * 关闭shiro自带的session
             * 详情见文档: http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
             */
            DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
            evaluator.setSessionStorageEnabled(false);
    
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            subjectDAO.setSessionStorageEvaluator(evaluator);
    
            securityManager.setSubjectDAO(subjectDAO);
    
            // 设置自定义Realm
            securityManager.setRealm(shiroRealm);
            return securityManager;
        
    
        /**
         * 过滤工厂
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) 
            ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
            factoryBean.setSecurityManager(securityManager);
            // 添加自己的过滤器并且取名为jwt
            Map<String, Filter> filters = new LinkedHashMap<>();
            filters.put("jwt", new JWTFilter());
    
            factoryBean.setFilters(filters);
    
            Map<String, String> filterChainDefinitionManager = new LinkedHashMap<>();
            // 所有请求通过我们自己的JWT Filter
            filterChainDefinitionManager.put("/**", "jwt");
            factoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
            return factoryBean;
        
    
        /**
         * 自动代理配置
         * @return
         */
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() 
            DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
            // 强制使用cglib,防止重复代理和可能引起代理出错的问题
            // https://zhuanlan.zhihu.com/p/29161098
            proxyCreator.setProxyTargetClass(true);
            return proxyCreator;
        
    
        /**
         * 开启注解支持
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor 以上是关于springboot整合shiro+jwt+redis详解的主要内容,如果未能解决你的问题,请参考以下文章

    SpringBoot整合Shiro+JWT实现认证及权限校验

    SpringBoot技术专题「权限校验专区」Shiro整合JWT授权和认证实现

    SpringBoot技术专题「实战开发系列」带你一同探索Shiro整合JWT授权和认证实战开发

    springboot整合shiro+jwt+redis详解

    教你 Shiro 整合 SpringBoot,避开各种坑

    Vue+SpringBoot超详细!一周开发一个SpringBoot + Vue+MybatisPlus+Shiro+JWT+Redis前后端分离个人博客项目!!!项目完结

(c)2006-2024 SYSTEM All Rights Reserved IT常识