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
Shiro
是apache
旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份
认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。Shiro架构图
Shiro 核心组件
用户、角色、权限之间的关系
- 用户拥有不同角色
- 角色拥有不同权限
1、
UsernamePasswordToken
,Shiro
用来封装用户登录信息,使用用户的登录信息来创建令牌Token
。
2、SecurityManager
,Shiro
的核心部分,负责安全认证和授权。
3、Suject
,Shiro
的一个抽象概念,包含了用户信息。
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
类,实现getPrincipal
和getCredentials
方法,(这两个方法本来是用于获取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
的整合,我们需要⾃定义⾃⼰的过滤器JWTFilter
,JWTFilter
继承了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
方法,因为是自己定义的Token
,shiro
无法识别,需要修改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
的相关配置,主要包括ShiroFilterFactoryBean
、DefaultWebSecurityManager
和Realm
的配置。@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授权和认证实战开发
Vue+SpringBoot超详细!一周开发一个SpringBoot + Vue+MybatisPlus+Shiro+JWT+Redis前后端分离个人博客项目!!!项目完结