Springboot系列之Shiro、JWT、Redis 进行认证鉴权

x33g5p2x  于2021-12-12 转载在 Spring  
字(23.1k)|赞(0)|评价(0)|浏览(518)

Springboot系列之Shiro、JWT、Redis 进行认证鉴权

Shiro架构

Apache Shiro是一个轻量级的安全框架

Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。 Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。其基本功能点如下图所示:

  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

接下来我们分别从外部和内部来看看Shiro的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的API, 且API契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。

可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject。

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

也就是说对于我们而言,最简单的一个Shiro应用:

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

从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。

接下来我们来从Shiro内部来看下Shiro的架构,如下图所示:

  • Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
  • SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
  • SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
  • SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

下面就开始代码实现Springboot Shiro JWT Redis认证鉴权(核心代码如下)

  1. Maven依赖pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>org.example</groupId>
  5. <artifactId>springboot-shrio-jwt</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. <parent>
  8. <artifactId>spring-boot-parent</artifactId>
  9. <groupId>org.springframework.boot</groupId>
  10. <version>2.1.3.RELEASE </version>
  11. </parent>
  12. <properties>
  13. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  14. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  15. <java.version>1.8</java.version>
  16. </properties>
  17. <dependencies>
  18. <!-- web -->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-web</artifactId>
  22. </dependency>
  23. <!-- mybatis-plus -->
  24. <dependency>
  25. <groupId>com.baomidou</groupId>
  26. <artifactId>mybatis-plus-boot-starter</artifactId>
  27. <version>3.1.2</version>
  28. </dependency>
  29. <!-- mysql -->
  30. <dependency>
  31. <groupId>mysql</groupId>
  32. <artifactId>mysql-connector-java</artifactId>
  33. <scope>runtime</scope>
  34. </dependency>
  35. <!-- spring热部署 -->
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-devtools</artifactId>
  39. <scope>compile</scope>
  40. <optional>true</optional>
  41. </dependency>
  42. <!-- lombok -->
  43. <dependency>
  44. <groupId>org.projectlombok</groupId>
  45. <artifactId>lombok</artifactId>
  46. <version>1.18.4</version>
  47. </dependency>
  48. <!-- druid -->
  49. <dependency>
  50. <groupId>com.alibaba</groupId>
  51. <artifactId>druid-spring-boot-starter</artifactId>
  52. <version>1.1.17</version>
  53. </dependency>
  54. <!-- fastjson -->
  55. <dependency>
  56. <groupId>com.alibaba</groupId>
  57. <artifactId>fastjson</artifactId>
  58. <version>1.2.58</version>
  59. </dependency>
  60. <!--shiro-->
  61. <dependency>
  62. <groupId>org.apache.shiro</groupId>
  63. <artifactId>shiro-spring-boot-starter</artifactId>
  64. <version> 1.4.1</version>
  65. </dependency>
  66. <!--JWT-->
  67. <dependency>
  68. <groupId>com.auth0</groupId>
  69. <artifactId>java-jwt</artifactId>
  70. <version>3.7.0</version>
  71. </dependency>
  72. <!-- Redis -->
  73. <dependency>
  74. <groupId>org.springframework.boot</groupId>
  75. <artifactId>spring-boot-starter-data-redis</artifactId>
  76. </dependency>
  77. <dependency>
  78. <groupId>org.apache.commons</groupId>
  79. <artifactId>commons-pool2</artifactId>
  80. </dependency>
  81. </dependencies>
  82. <build>
  83. <plugins>
  84. <plugin>
  85. <groupId>org.apache.maven.plugins</groupId>
  86. <artifactId>maven-compiler-plugin</artifactId>
  87. <configuration>
  88. <source>1.8</source>
  89. <target>1.8</target>
  90. </configuration>
  91. </plugin>
  92. </plugins>
  93. </build>
  94. </project>
  1. 核心配置类
  1. package com.kongliand.shiro.config;
  2. import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
  3. import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
  4. import org.mybatis.spring.annotation.MapperScan;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. /** * mybatis-plus配置类 */
  8. @Configuration
  9. @MapperScan(value = {"com.kongliand.shiro.mapper"})
  10. public class MybatisPlusConfig {
  11. /** * 分页插件 */
  12. @Bean
  13. public PaginationInterceptor paginationInterceptor() {
  14. return new PaginationInterceptor();
  15. }
  16. /** * mybatis-plus SQL执行效率插件【生产环境可以关闭】 */
  17. @Bean
  18. public PerformanceInterceptor performanceInterceptor() {
  19. return new PerformanceInterceptor();
  20. }
  21. }

  1. package com.kongliand.shiro.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
  3. import com.fasterxml.jackson.annotation.PropertyAccessor;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
  6. import org.springframework.cache.CacheManager;
  7. import org.springframework.cache.annotation.CachingConfigurerSupport;
  8. import org.springframework.cache.annotation.EnableCaching;
  9. import org.springframework.cache.interceptor.KeyGenerator;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  13. import org.springframework.data.redis.cache.RedisCacheManager;
  14. import org.springframework.data.redis.cache.RedisCacheWriter;
  15. import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
  16. import org.springframework.data.redis.core.RedisTemplate;
  17. import org.springframework.data.redis.serializer.*;
  18. import javax.annotation.Resource;
  19. import java.lang.reflect.Method;
  20. import java.time.Duration;
  21. import java.util.Arrays;
  22. import static java.util.Collections.singletonMap;
  23. /** * redis核心配置类 */
  24. @Configuration
  25. @EnableCaching // 开启缓存支持
  26. public class RedisConfig extends CachingConfigurerSupport {
  27. @Resource
  28. private LettuceConnectionFactory lettuceConnectionFactory;
  29. /** * 自定义策略生成的key * 自定义的缓存key的生成策略 若想使用这个key * 只需要讲注解上keyGenerator的值设置为keyGenerator即可</br> */
  30. @Override
  31. @Bean
  32. public KeyGenerator keyGenerator() {
  33. return new KeyGenerator() {
  34. @Override
  35. public Object generate(Object target, Method method, Object... params) {
  36. StringBuilder sb = new StringBuilder();
  37. sb.append(target.getClass().getName());
  38. sb.append(method.getDeclaringClass().getName());
  39. Arrays.stream(params).map(Object::toString).forEach(sb::append);
  40. return sb.toString();
  41. }
  42. };
  43. }
  44. /** * RedisTemplate配置 */
  45. @Bean
  46. public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
  47. // 设置序列化
  48. Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
  49. ObjectMapper om = new ObjectMapper();
  50. om.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
  51. om.enableDefaultTyping(DefaultTyping.NON_FINAL);
  52. jackson2JsonRedisSerializer.setObjectMapper(om);
  53. // 配置redisTemplate
  54. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
  55. redisTemplate.setConnectionFactory(lettuceConnectionFactory);
  56. RedisSerializer<?> stringSerializer = new StringRedisSerializer();
  57. redisTemplate.setKeySerializer(stringSerializer);// key序列化
  58. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
  59. redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
  60. redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
  61. redisTemplate.afterPropertiesSet();
  62. return redisTemplate;
  63. }
  64. /** * 缓存配置管理器 */
  65. @Bean
  66. public CacheManager cacheManager(LettuceConnectionFactory factory) {
  67. // 配置序列化
  68. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
  69. RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
  70. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
  71. // 以锁写入的方式创建RedisCacheWriter对象
  72. //RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
  73. // 创建默认缓存配置对象
  74. /* 默认配置,设置缓存有效期 1小时*/
  75. //RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
  76. /* 配置test的超时时间为120s*/
  77. RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration)
  78. .withInitialCacheConfigurations(singletonMap("test", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120)).disableCachingNullValues()))
  79. .transactionAware().build();
  80. return cacheManager;
  81. }
  82. }
  1. package com.kongliand.shiro.config;
  2. import com.kongliand.shiro.filter.JwtFilter;
  3. import com.kongliand.shiro.shiro.ShiroRealm;
  4. import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
  5. import org.apache.shiro.mgt.DefaultSubjectDAO;
  6. import org.apache.shiro.mgt.SecurityManager;
  7. import org.apache.shiro.spring.LifecycleBeanPostProcessor;
  8. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  9. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  10. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  11. import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
  12. import org.springframework.context.annotation.Bean;
  13. import org.springframework.context.annotation.Configuration;
  14. import org.springframework.context.annotation.DependsOn;
  15. import javax.servlet.Filter;
  16. import java.util.HashMap;
  17. import java.util.LinkedHashMap;
  18. import java.util.Map;
  19. /** * @desc: shiro 配置类 * @author kevin */
  20. @Configuration
  21. public class ShiroConfig {
  22. /** * Filter Chain定义说明 * <p> * 1、一个URL可以配置多个Filter,使用逗号分隔 * 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles */
  23. @Bean("shiroFilter")
  24. public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
  25. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  26. shiroFilterFactoryBean.setSecurityManager(securityManager);
  27. // 拦截器
  28. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
  29. // 配置不会被拦截的链接 顺序判断
  30. filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
  31. filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
  32. filterChainDefinitionMap.put("/", "anon");
  33. filterChainDefinitionMap.put("/**/*.js", "anon");
  34. filterChainDefinitionMap.put("/**/*.css", "anon");
  35. filterChainDefinitionMap.put("/**/*.html", "anon");
  36. filterChainDefinitionMap.put("/**/*.jpg", "anon");
  37. filterChainDefinitionMap.put("/**/*.png", "anon");
  38. filterChainDefinitionMap.put("/**/*.ico", "anon");
  39. filterChainDefinitionMap.put("/druid/**", "anon");
  40. filterChainDefinitionMap.put("/user/test", "anon"); //测试
  41. // 添加自己的过滤器并且取名为jwt
  42. Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
  43. filterMap.put("jwt", new JwtFilter());
  44. shiroFilterFactoryBean.setFilters(filterMap);
  45. // <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
  46. filterChainDefinitionMap.put("/**", "jwt");
  47. // 未授权界面返回JSON
  48. shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
  49. shiroFilterFactoryBean.setLoginUrl("/sys/common/403");
  50. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  51. return shiroFilterFactoryBean;
  52. }
  53. @Bean("securityManager")
  54. public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
  55. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  56. securityManager.setRealm(myRealm);
  57. /* * 关闭shiro自带的session,详情见文档 * http://shiro.apache.org/session-management.html#SessionManagement- * StatelessApplications%28Sessionless%29 */
  58. DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
  59. DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
  60. defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
  61. subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
  62. securityManager.setSubjectDAO(subjectDAO);
  63. return securityManager;
  64. }
  65. /** * 下面的代码是添加注解支持 * * @return */
  66. @Bean
  67. @DependsOn("lifecycleBeanPostProcessor")
  68. public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  69. DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
  70. defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
  71. return defaultAdvisorAutoProxyCreator;
  72. }
  73. @Bean
  74. public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  75. return new LifecycleBeanPostProcessor();
  76. }
  77. @Bean
  78. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
  79. AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
  80. advisor.setSecurityManager(securityManager);
  81. return advisor;
  82. }
  83. }

3.鉴权登录拦截器

  1. package com.kongliand.shiro.filter;
  2. import com.kongliand.shiro.constant.CommonConstant;
  3. import com.kongliand.shiro.entity.JwtToken;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.apache.shiro.authc.AuthenticationException;
  6. import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
  7. import org.springframework.http.HttpStatus;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. import javax.servlet.ServletRequest;
  10. import javax.servlet.ServletResponse;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. /** * 鉴权登录拦截器 **/
  14. @Slf4j
  15. public class JwtFilter extends BasicHttpAuthenticationFilter {
  16. /** * 执行登录认证 * * @param request * @param response * @param mappedValue * @return */
  17. @Override
  18. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  19. try {
  20. executeLogin(request, response);
  21. return true;
  22. } catch (Exception e) {
  23. throw new AuthenticationException(e.getMessage());
  24. }
  25. }
  26. /** * */
  27. @Override
  28. protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
  29. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  30. String token = httpServletRequest.getHeader(CommonConstant.ACCESS_TOKEN);
  31. JwtToken jwtToken = new JwtToken(token);
  32. // 提交给realm进行登入,如果错误他会抛出异常并被捕获
  33. getSubject(request, response).login(jwtToken);
  34. // 如果没有抛出异常则代表登入成功,返回true
  35. return true;
  36. }
  37. /** * 对跨域提供支持 */
  38. @Override
  39. protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
  40. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  41. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  42. httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
  43. httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
  44. httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
  45. // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
  46. if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
  47. httpServletResponse.setStatus(HttpStatus.OK.value());
  48. return false;
  49. }
  50. return super.preHandle(request, response);
  51. }
  52. }

4.用户登录鉴权和获取用户授权

  1. package com.kongliand.shiro.shiro;
  2. import com.kongliand.shiro.constant.CommonConstant;
  3. import com.kongliand.shiro.entity.JwtToken;
  4. import com.kongliand.shiro.entity.SysUser;
  5. import com.kongliand.shiro.service.ISysUserService;
  6. import com.kongliand.shiro.util.CommonUtils;
  7. import com.kongliand.shiro.util.JwtUtil;
  8. import com.kongliand.shiro.util.RedisUtil;
  9. import com.kongliand.shiro.util.SpringContextUtils;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.apache.shiro.authc.AuthenticationException;
  12. import org.apache.shiro.authc.AuthenticationInfo;
  13. import org.apache.shiro.authc.AuthenticationToken;
  14. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  15. import org.apache.shiro.authz.AuthorizationInfo;
  16. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  17. import org.apache.shiro.realm.AuthorizingRealm;
  18. import org.apache.shiro.subject.PrincipalCollection;
  19. import org.springframework.beans.BeanUtils;
  20. import org.springframework.beans.factory.annotation.Autowired;
  21. import org.springframework.context.annotation.Lazy;
  22. import org.springframework.stereotype.Component;
  23. import java.util.Set;
  24. /** * 用户登录鉴权和获取用户授权 */
  25. @Component
  26. @Slf4j
  27. public class ShiroRealm extends AuthorizingRealm {
  28. @Autowired
  29. @Lazy
  30. private ISysUserService sysUserService;
  31. @Autowired
  32. @Lazy
  33. private RedisUtil redisUtil;
  34. /** * 必须重写此方法,不然Shiro会报错 */
  35. @Override
  36. public boolean supports(AuthenticationToken token) {
  37. return token instanceof JwtToken;
  38. }
  39. /** * 功能: 获取用户权限信息,包括角色以及权限。只有当触发检测用户权限时才会调用此方法,例如checkRole,checkPermission * * @param principals token * @return AuthorizationInfo 权限信息 */
  40. @Override
  41. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  42. log.info("————权限认证 [ roles、permissions]————");
  43. SysUser sysUser = null;
  44. String username = null;
  45. if (principals != null) {
  46. sysUser = (SysUser) principals.getPrimaryPrincipal();
  47. username = sysUser.getUserName();
  48. }
  49. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  50. // 设置用户拥有的角色集合,比如“admin,test”
  51. Set<String> roleSet = sysUserService.getUserRolesSet(username);
  52. info.setRoles(roleSet);
  53. // 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
  54. Set<String> permissionSet = sysUserService.getUserPermissionsSet(username);
  55. info.addStringPermissions(permissionSet);
  56. return info;
  57. }
  58. /** * 功能: 用来进行身份认证,也就是说验证用户输入的账号和密码是否正确,获取身份验证信息,错误抛出异常 * * @param auth 用户身份信息 token * @return 返回封装了用户信息的 AuthenticationInfo 实例 */
  59. @Override
  60. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
  61. String token = (String) auth.getCredentials();
  62. if (token == null) {
  63. log.info("————————身份认证失败——————————IP地址: " + CommonUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
  64. throw new AuthenticationException("token为空!");
  65. }
  66. // 校验token有效性
  67. SysUser loginUser = this.checkUserTokenIsEffect(token);
  68. return new SimpleAuthenticationInfo(loginUser, token, getName());
  69. }
  70. /** * 校验token的有效性 * * @param token */
  71. public SysUser checkUserTokenIsEffect(String token) throws AuthenticationException {
  72. // 解密获得username,用于和数据库进行对比
  73. String username = JwtUtil.getUsername(token);
  74. if (username == null) {
  75. throw new AuthenticationException("token非法无效!");
  76. }
  77. // 查询用户信息
  78. SysUser loginUser = new SysUser();
  79. SysUser sysUser = sysUserService.getUserByName(username);
  80. if (sysUser == null) {
  81. throw new AuthenticationException("用户不存在!");
  82. }
  83. // 校验token是否超时失效 & 或者账号密码是否错误
  84. if (!jwtTokenRefresh(token, username, sysUser.getPassWord())) {
  85. throw new AuthenticationException("Token失效请重新登录!");
  86. }
  87. // 判断用户状态
  88. if (!"0".equals(sysUser.getDelFlag())) {
  89. throw new AuthenticationException("账号已被删除,请联系管理员!");
  90. }
  91. BeanUtils.copyProperties(sysUser, loginUser);
  92. return loginUser;
  93. }
  94. /** * JWTToken刷新生命周期 (解决用户一直在线操作,提供Token失效问题) * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样) * 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证 * 3、当该用户这次请求JWTToken值还在生命周期内,则会通过重新PUT的方式k、v都为Token值,缓存中的token值生命周期时间重新计算(这时候k、v值一样) * 4、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算 * 5、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。 * 6、每次当返回为true情况下,都会给Response的Header中设置Authorization,该Authorization映射的v为cache对应的v值。 * 7、注:当前端接收到Response的Header中的Authorization值会存储起来,作为以后请求token使用 * * @param userName * @param passWord * @return */
  95. public boolean jwtTokenRefresh(String token, String userName, String passWord) {
  96. String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
  97. if (CommonUtils.isNotEmpty(cacheToken)) {
  98. // 校验token有效性
  99. if (!JwtUtil.verify(cacheToken, userName, passWord)) {
  100. String newAuthorization = JwtUtil.sign(userName, passWord);
  101. redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
  102. // 设置超时时间
  103. redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
  104. } else {
  105. redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
  106. // 设置超时时间
  107. redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
  108. }
  109. return true;
  110. }
  111. return false;
  112. }
  113. }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wdbq8hpS-1639299147506)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

5.application.yml配置信息

  1. server:
  2. port: 8088
  3. spring:
  4. datasource:
  5. type: com.alibaba.druid.pool.DruidDataSource
  6. driver-class-name: com.mysql.cj.jdbc.Driver
  7. druid:
  8. url: jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
  9. username: root
  10. password: admin123
  11. initial-size: 10
  12. max-active: 100
  13. min-idle: 10
  14. max-wait: 60000
  15. pool-prepared-statements: true
  16. max-pool-prepared-statement-per-connection-size: 20
  17. time-between-eviction-runs-millis: 60000
  18. min-evictable-idle-time-millis: 300000
  19. #validation-query: SELECT 1 FROM DUAL
  20. test-while-idle: true
  21. test-on-borrow: false
  22. test-on-return: false
  23. stat-view-servlet:
  24. enabled: true
  25. url-pattern: /druid/*
  26. login-username: admin
  27. login-password: admin
  28. filter:
  29. stat:
  30. log-slow-sql: true
  31. slow-sql-millis: 1000
  32. merge-sql: false
  33. wall:
  34. config:
  35. multi-statement-allow: true
  36. #redis配置
  37. redis:
  38. database: 0
  39. host: 127.0.0.1
  40. lettuce:
  41. pool:
  42. max-active: 8 #最大连接数据库连接数,设 0 为没有限制
  43. max-idle: 8 #最大等待连接中的数量,设 0 为没有限制
  44. max-wait: -1ms #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
  45. min-idle: 0 #最小等待连接中的数量,设 0 为没有限制
  46. shutdown-timeout: 100ms
  47. password: ''
  48. port: 6379
  49. #mybatis plus设置
  50. mybatis-plus:
  51. type-aliases-package: com.kongliand.shiro.entity
  52. mapper-locations: classpath:mapper/*.xml
  53. global-config:
  54. banner: false
  55. db-config:
  56. #主键类型
  57. id-type: auto
  58. # 默认数据库表下划线命名
  59. table-underline: true
  60. configuration:
  61. map-underscore-to-camel-case: true
  62. # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
  63. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  64. #日志配置
  65. logging:
  66. level:
  67. com.kongliand.shiro.mapper: debug

最后开始验证一下
1.获取token


2.根据token请求接口

相关文章