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
<?xml version="1.0" encoding="UTF-8"?>
<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"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>springboot-shrio-jwt</artifactId><version>1.0-SNAPSHOT</version><parent><artifactId>spring-boot-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.1.3.RELEASE </version></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><!-- web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.2</version></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- spring热部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>compile</scope><optional>true</optional></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency><!-- druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version></dependency><!-- fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency><!--shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version> 1.4.1</version></dependency><!--JWT--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.7.0</version></dependency><!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build>
</project>
  1. 核心配置类
  package com.kongliand.shiro.config;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/*** mybatis-plus配置类*/@Configuration@MapperScan(value = {"com.kongliand.shiro.mapper"})public class MybatisPlusConfig {/*** 分页插件*/@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}/*** mybatis-plus SQL执行效率插件【生产环境可以关闭】*/@Beanpublic PerformanceInterceptor performanceInterceptor() {return new PerformanceInterceptor();}}

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

3.鉴权登录拦截器

package com.kongliand.shiro.filter;import com.kongliand.shiro.constant.CommonConstant;
import com.kongliand.shiro.entity.JwtToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 鉴权登录拦截器**/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {/*** 执行登录认证** @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {executeLogin(request, response);return true;} catch (Exception e) {throw new AuthenticationException(e.getMessage());}}/****/@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader(CommonConstant.ACCESS_TOKEN);JwtToken jwtToken = new JwtToken(token);// 提交给realm进行登入,如果错误他会抛出异常并被捕获getSubject(request, response).login(jwtToken);// 如果没有抛出异常则代表登入成功,返回truereturn true;}/*** 对跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;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);}
}

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

package com.kongliand.shiro.shiro;import com.kongliand.shiro.constant.CommonConstant;
import com.kongliand.shiro.entity.JwtToken;
import com.kongliand.shiro.entity.SysUser;
import com.kongliand.shiro.service.ISysUserService;
import com.kongliand.shiro.util.CommonUtils;
import com.kongliand.shiro.util.JwtUtil;
import com.kongliand.shiro.util.RedisUtil;
import com.kongliand.shiro.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;import java.util.Set;/*** 用户登录鉴权和获取用户授权*/
@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {@Autowired@Lazyprivate ISysUserService sysUserService;@Autowired@Lazyprivate RedisUtil redisUtil;/*** 必须重写此方法,不然Shiro会报错*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 功能: 获取用户权限信息,包括角色以及权限。只有当触发检测用户权限时才会调用此方法,例如checkRole,checkPermission** @param principals token* @return AuthorizationInfo 权限信息*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {log.info("————权限认证 [ roles、permissions]————");SysUser sysUser = null;String username = null;if (principals != null) {sysUser = (SysUser) principals.getPrimaryPrincipal();username = sysUser.getUserName();}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 设置用户拥有的角色集合,比如“admin,test”Set<String> roleSet = sysUserService.getUserRolesSet(username);info.setRoles(roleSet);// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”Set<String> permissionSet = sysUserService.getUserPermissionsSet(username);info.addStringPermissions(permissionSet);return info;}/*** 功能: 用来进行身份认证,也就是说验证用户输入的账号和密码是否正确,获取身份验证信息,错误抛出异常** @param auth 用户身份信息 token* @return 返回封装了用户信息的 AuthenticationInfo 实例*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {String token = (String) auth.getCredentials();if (token == null) {log.info("————————身份认证失败——————————IP地址:  " + CommonUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));throw new AuthenticationException("token为空!");}// 校验token有效性SysUser loginUser = this.checkUserTokenIsEffect(token);return new SimpleAuthenticationInfo(loginUser, token, getName());}/*** 校验token的有效性** @param token*/public SysUser checkUserTokenIsEffect(String token) throws AuthenticationException {// 解密获得username,用于和数据库进行对比String username = JwtUtil.getUsername(token);if (username == null) {throw new AuthenticationException("token非法无效!");}// 查询用户信息SysUser loginUser = new SysUser();SysUser sysUser = sysUserService.getUserByName(username);if (sysUser == null) {throw new AuthenticationException("用户不存在!");}// 校验token是否超时失效 & 或者账号密码是否错误if (!jwtTokenRefresh(token, username, sysUser.getPassWord())) {throw new AuthenticationException("Token失效请重新登录!");}// 判断用户状态if (!"0".equals(sysUser.getDelFlag())) {throw new AuthenticationException("账号已被删除,请联系管理员!");}BeanUtils.copyProperties(sysUser, loginUser);return loginUser;}/*** 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*/public boolean jwtTokenRefresh(String token, String userName, String passWord) {String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));if (CommonUtils.isNotEmpty(cacheToken)) {// 校验token有效性if (!JwtUtil.verify(cacheToken, userName, passWord)) {String newAuthorization = JwtUtil.sign(userName, passWord);redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);// 设置超时时间redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);} else {redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);// 设置超时时间redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);}return true;}return false;}}

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

5.application.yml配置信息

server:port: 8088spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverdruid:url: jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTCusername: rootpassword: admin123initial-size: 10max-active: 100min-idle: 10max-wait: 60000pool-prepared-statements: truemax-pool-prepared-statement-per-connection-size: 20time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000#validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: falsetest-on-return: falsestat-view-servlet:enabled: trueurl-pattern: /druid/*login-username: adminlogin-password: adminfilter:stat:log-slow-sql: trueslow-sql-millis: 1000merge-sql: falsewall:config:multi-statement-allow: true#redis配置redis:database: 0host: 127.0.0.1lettuce:pool:max-active: 8   #最大连接数据库连接数,设 0 为没有限制max-idle: 8     #最大等待连接中的数量,设 0 为没有限制max-wait: -1ms  #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。min-idle: 0     #最小等待连接中的数量,设 0 为没有限制shutdown-timeout: 100mspassword: ''port: 6379#mybatis plus设置
mybatis-plus:type-aliases-package: com.kongliand.shiro.entitymapper-locations: classpath:mapper/*.xmlglobal-config:banner: falsedb-config:#主键类型id-type: auto# 默认数据库表下划线命名table-underline: trueconfiguration:map-underscore-to-camel-case: true# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl#日志配置
logging:level:com.kongliand.shiro.mapper: debug

最后开始验证一下
1.获取token
2.根据token请求接口

Springboot系列之Shiro、JWT、Redis 进行认证鉴权相关推荐

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

    项目目录 资源准备 前后端分离项目 技术栈 Java后端接口开发 1.前言 2.新建Springboot项目 3.整合mybatis plus 3.统一结果封装 4.整合shiro+jwt,并会话共享 ...

  2. java 鉴权_我爱java系列之---【JWT实现微服务鉴权(一)】

    JWT介绍 JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 一个JWT实际上就是一个字符串,它由三部分组成,头部.载荷与签 ...

  3. SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (三) 鉴权

    项目Github地址: https://github.com/baiye21/ShiroDemo SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (一) 简介与配置 S ...

  4. Shiro+JWT+Redis实现用户校验

    Shiro基础知识 1. shiro基本功能 认证:验证用户登录认证: 授权:即权限验证,对已经登录的用户验证是否有相应的权限: 会话管理:用户在认证成功之后创建会话,当前用户的所有信息都会保存在这个 ...

  5. java鉴权_一个开箱即用的高效认证鉴权框架,专注于restful api的认证鉴权动态保护...

    作者:tomsun28 来源:SegmentFault 思否 写在开头 看了看这个专栏的最近一篇文章已经是两年前了,时间过得好快.应该是出学校后时间就很快了.两年前因为用shiro后,自己就按着想法开 ...

  6. 认证鉴权与API权限控制在微服务架构中的设计与实现

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. 背景 最近在做权限相关服务的开发, ...

  7. 认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    作者: [Aoho's Blog] 引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. ...

  8. Spring Cloud与微服务学习总结(3)——认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    本文转载自(http://blueskykong.com/2017/10/19/security1/) 1. 背景 最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安 ...

  9. api postmain 鉴权_认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. 背景 最近在做权限相关服务的开发, ...

最新文章

  1. HBase键值分片的简单运用
  2. MacBook 推出移动硬盘时总是提示有程序在使用它,如何解决?
  3. 数据结构C语言实现动态顺序表
  4. Django通过pycharm创建后,如何登录admin后台?
  5. mysql登陆时报错PID_Mysql 启动错误:the server quit without updating pid
  6. 10种电脑无法启动故障
  7. MakeDirZ.bat
  8. RAID结构介绍以及RAID1、RAID0等各种模式的区别
  9. Apache POI简单入门
  10. 怎么用PDF转换器将PDF文件转成txt
  11. 【Unity基础】Unity打包exe
  12. linux服务器搭建开源蝉道二进制
  13. 第三方调试助手的与S7-1200 PLC的通信
  14. python 处理 图像和视频
  15. 深入浅出程序设计竞赛(基础篇)
  16. 零基础学习人工智能如何入门?
  17. 机器学习 AI 谷歌ML Kit 与苹果Core ML
  18. ftp工具破解版,你知道有哪几款好用的ftp工具破解版吗
  19. 百分百解决 mbatis/mp报错 Invalid bound statement (not found)
  20. Redis可视化管理工具:Another Redis DeskTop Manager

热门文章

  1. [转载] java避免空指针异常_第1部分:在现代Java应用程序中避免空指针异常
  2. java iterator_Java ArrayDeque iterator()方法与示例
  3. c程序预处理器的设计与实现_C预处理器-能力问题与解答
  4. Python程序检查字符串是否是回文
  5. echarts 柱状图不显示y坐标轴_Python+matplotlib自定义坐标轴位置、颜色、箭头
  6. linux dd入门,Linux基础知识:Linux中DD命令详解
  7. c语言负数左移右移_C语言 位运算符的运算规则
  8. dnf韩服服务器维护中,DNF2019韩服4.30维护:这些职业被加强
  9. java正则表达式 ppt_Java正则表达式演示
  10. 【C++】For循环同时初始化两个变量