SpringBoot + SpringSecurity + Mybatis-Plus + JWT + Redis 实现分布式系统认证和授权(刷新Token和Token黑名单)

1. 前提#

  本文在基于SpringBoot整合SpringSecurity实现JWT的前提中添加刷新Token以及添加Token黑名单。在浏览之前,请查看博客:
  SpringBoot + SpringSecurity + Mybatis-Plus + JWT实现分布式系统认证和授权

2. 添加Redis依赖及配置#

Copy
<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>com.c3stones</groupId> <artifactId>spring-security-jwt-redis-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-security-jwt-redis-demo</name> <description>Spring Boot + Srping Security + Mybatis-Plus + JWT + Redis Demo</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <jjwt.version>0.9.0</jjwt.version> <druid.version>1.1.6</druid.version> <jwt.version>1.0.9.RELEASE</jwt.version> <fastjson.version>1.2.45</fastjson.version> <mybatis-plus.version>3.3.1</mybatis-plus.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--Spring Security依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Mybatis-Plus 依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Druid 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!-- StringUtils 工具 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!-- JSON工具 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <!-- JWT依赖 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency> <!-- Redis依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
  • 修改application.yml,添加Redis配置及刷新时间配置
Copy
server: port: 8080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull username: root password: 123456 redis: host: 127.0.0.1 port: 6379 password: 123456 # JWT配置 jwt: # 密匙Key secret: JWTSecret,C3Stones # HeaderKey tokenHeader: Authorization # Token前缀 tokenPrefix: Bearer # 过期时间,单位秒 expiration: 300 # 刷新时间,单位天 refreshTime: 7 # 配置白名单(不需要认证) antMatchers: /login/**,/register/**,/static/** # Mybatis-plus配置 mybatis-plus: mapper-locations: classpath:mapper/*.xml global-config: db-config: id-type: AUTO configuration: # 打印sql log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 添加Redis工具类
Copy
import java.util.HashMap; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; /** * Redis工具类 * * @author CL * */ @Component public class RedisUtils { @Autowired private StringRedisTemplate redisTemplate; private static RedisUtils redisUtils; /** * 初始化 */ @PostConstruct public void init() { redisUtils = this; redisUtils.redisTemplate = this.redisTemplate; } /** * 查询key,支持模糊查询 * * @param key */ public static Set<String> keys(String key) { return redisUtils.redisTemplate.keys(key); } /** * 获取值 * * @param key */ public static Object get(String key) { return redisUtils.redisTemplate.opsForValue().get(key); } /** * 设置值 * * @param key * @param value */ public static void set(String key, String value) { redisUtils.redisTemplate.opsForValue().set(key, value); } /** * 设置值,并设置过期时间 * * @param key * @param value * @param expire 过期时间,单位秒 */ public static void set(String key, String value, Integer expire) { redisUtils.redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS); } /** * 删出key * * @param key */ public static void delete(String key) { redisUtils.redisTemplate.opsForValue().getOperations().delete(key); } /** * 设置对象 * * @param key key * @param hashKey hashKey * @param object 对象 */ public static void hset(String key, String hashKey, Object object) { redisUtils.redisTemplate.opsForHash().put(key, hashKey, object); } /** * 设置对象 * * @param key key * @param hashKey hashKey * @param object 对象 * @param expire 过期时间,单位秒 */ public static void hset(String key, String hashKey, Object object, Integer expire) { redisUtils.redisTemplate.opsForHash().put(key, hashKey, object); redisUtils.redisTemplate.expire(key, expire, TimeUnit.SECONDS); } /** * 设置HashMap * * @param key key * @param map map值 */ public static void hset(String key, HashMap<String, Object> map) { redisUtils.redisTemplate.opsForHash().putAll(key, map); } /** * key不存在时设置值 * * @param key * @param hashKey * @param object */ public static void hsetAbsent(String key, String hashKey, Object object) { redisUtils.redisTemplate.opsForHash().putIfAbsent(key, hashKey, object); } /** * 获取Hash值 * * @param key * @param hashKey * @return */ public static Object hget(String key, String hashKey) { return redisUtils.redisTemplate.opsForHash().get(key, hashKey); } /** * 获取key的所有值 * * @param key * @return */ public static Object hget(String key) { return redisUtils.redisTemplate.opsForHash().entries(key); } /** * 删除key的所有值 * * @param key */ public static void deleteKey(String key) { redisUtils.redisTemplate.opsForHash().getOperations().delete(key); } /** * 判断key下是否有值 * * @param key */ public static Boolean hasKey(String key) { return redisUtils.redisTemplate.opsForHash().getOperations().hasKey(key); } /** * 判断key和hasKey下是否有值 * * @param key * @param hasKey */ public static Boolean hasKey(String key, String hasKey) { return redisUtils.redisTemplate.opsForHash().hasKey(key, hasKey); } }
  • 添加获取请求IP地址工具类
Copy
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * 获取请求IP地址工具类 * * @author CL * */ @Component public class AccessAddressUtils { /** * 获取用户真实IP地址 * * @param request * @return */ public static String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }

3. 核心类修改#

  • 修改JWT配置类,并添加刷新时间属性
Copy
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * JWT配置基础类 * * @author CL * */ @Component @ConfigurationProperties(prefix = "jwt") @SuppressWarnings("static-access") public class JWTConfig { /** * 密匙Key */ public static String secret; /** * HeaderKey */ public static String tokenHeader; /** * Token前缀 */ public static String tokenPrefix; /** * 过期时间 */ public static Integer expiration; /** * 有效时间 */ public static Integer refreshTime; /** * 配置白名单 */ public static String antMatchers; /** * 将过期时间单位换算成毫秒 * * @param expiration 过期时间,单位秒 */ public void setExpiration(Integer expiration) { this.expiration = expiration * 1000; } /** * 将有效时间单位换算成毫秒 * * @param validTime 有效时间,单位秒 */ public void setRefreshTime(Integer refreshTime) { this.refreshTime = refreshTime * 24 * 60 * 60 * 1000; } public void setSecret(String secret) { this.secret = secret; } public void setTokenHeader(String tokenHeader) { this.tokenHeader = tokenHeader; } public void setTokenPrefix(String tokenPrefix) { this.tokenPrefix = tokenPrefix + " "; } public void setAntMatchers(String antMatchers) { this.antMatchers = antMatchers; } }
  • 修改JWTToken工具类
Copy
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.c3stones.security.config.JWTConfig; import com.c3stones.security.entity.SysUserDetails; import com.c3stones.security.service.SysUserDetailsService; import com.c3stones.utils.RedisUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.extern.slf4j.Slf4j; /** * JWT生产Token工具类 * * @author CL * */ @Slf4j @Component public class JWTTokenUtils { /** * 时间格式化 */ private static final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Autowired private SysUserDetailsService sysUserDetailsService; private static JWTTokenUtils jwtTokenUtils; @PostConstruct public void init() { jwtTokenUtils = this; jwtTokenUtils.sysUserDetailsService = this.sysUserDetailsService; } /** * 创建Token * * @param sysUserDetails 用户信息 * @return */ public static String createAccessToken(SysUserDetails sysUserDetails) { String token = Jwts.builder()// 设置JWT .setId(sysUserDetails.getId().toString()) // 用户Id .setSubject(sysUserDetails.getUsername()) // 主题 .setIssuedAt(new Date()) // 签发时间 .setIssuer("C3Stones") // 签发者 .setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration)) // 过期时间 .signWith(SignatureAlgorithm.HS512, JWTConfig.secret) // 签名算法、密钥 .claim("authorities", JSON.toJSONString(sysUserDetails.getAuthorities()))// 自定义其他属性,如用户组织机构ID,用户所拥有的角色,用户权限信息等 .claim("ip", sysUserDetails.getIp()).compact(); return JWTConfig.tokenPrefix + token; } /** * 刷新Token * * @param oldToken 过期但未超过刷新时间的Token * @return */ public static String refreshAccessToken(String oldToken) { String username = JWTTokenUtils.getUserNameByToken(oldToken); SysUserDetails sysUserDetails = (SysUserDetails) jwtTokenUtils.sysUserDetailsService .loadUserByUsername(username); sysUserDetails.setIp(JWTTokenUtils.getIpByToken(oldToken)); return createAccessToken(sysUserDetails); } /** * 解析Token * * @param token Token信息 * @return */ public static SysUserDetails parseAccessToken(String token) { SysUserDetails sysUserDetails = null; if (StringUtils.isNotEmpty(token)) { try { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); // 解析Token Claims claims = Jwts.parser().setSigningKey(JWTConfig.secret).parseClaimsJws(token).getBody(); // 获取用户信息 sysUserDetails = new SysUserDetails(); sysUserDetails.setId(Long.parseLong(claims.getId())); sysUserDetails.setUsername(claims.getSubject()); // 获取角色 Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(); String authority = claims.get("authorities").toString(); if (StringUtils.isNotEmpty(authority)) { List<Map<String, String>> authorityList = JSON.parseObject(authority, new TypeReference<List<Map<String, String>>>() { }); for (Map<String, String> role : authorityList) { if (!role.isEmpty()) { authorities.add(new SimpleGrantedAuthority(role.get("authority"))); } } } sysUserDetails.setAuthorities(authorities); // 获取IP String ip = claims.get("ip").toString(); sysUserDetails.setIp(ip); } catch (Exception e) { log.error("解析Token异常:" + e); } } return sysUserDetails; } /** * 保存Token信息到Redis中 * * @param token Token信息 * @param username 用户名 * @param ip IP */ public static void setTokenInfo(String token, String username, String ip) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); Integer refreshTime = JWTConfig.refreshTime; LocalDateTime localDateTime = LocalDateTime.now(); RedisUtils.hset(token, "username", username, refreshTime); RedisUtils.hset(token, "ip", ip, refreshTime); RedisUtils.hset(token, "refreshTime", df.format(localDateTime.plus(JWTConfig.refreshTime, ChronoUnit.MILLIS)), refreshTime); RedisUtils.hset(token, "expiration", df.format(localDateTime.plus(JWTConfig.expiration, ChronoUnit.MILLIS)), refreshTime); } } /** * 将Token放到黑名单中 * * @param token Token信息 */ public static void addBlackList(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); RedisUtils.hset("blackList", token, df.format(LocalDateTime.now())); } } /** * Redis中删除Token * * @param token Token信息 */ public static void deleteRedisToken(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); RedisUtils.deleteKey(token); } } /** * 判断当前Token是否在黑名单中 * * @param token Token信息 */ public static boolean isBlackList(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); return RedisUtils.hasKey("blackList", token); } return false; } /** * 是否过期 * * @param expiration 过期时间,字符串 * @return 过期返回True,未过期返回false */ public static boolean isExpiration(String expiration) { LocalDateTime expirationTime = LocalDateTime.parse(expiration, df); LocalDateTime localDateTime = LocalDateTime.now(); if (localDateTime.compareTo(expirationTime) > 0) { return true; } return false; } /** * 是否有效 * * @param refreshTime 刷新时间,字符串 * @return 有效返回True,无效返回false */ public static boolean isValid(String refreshTime) { LocalDateTime validTime = LocalDateTime.parse(refreshTime, df); LocalDateTime localDateTime = LocalDateTime.now(); if (localDateTime.compareTo(validTime) > 0) { return false; } return true; } /** * 检查Redis中是否存在Token * * @param token Token信息 * @return */ public static boolean hasToken(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); return RedisUtils.hasKey(token); } return false; } /** * 从Redis中获取过期时间 * * @param token Token信息 * @return 过期时间,字符串 */ public static String getExpirationByToken(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); return RedisUtils.hget(token, "expiration").toString(); } return null; } /** * 从Redis中获取刷新时间 * * @param token Token信息 * @return 刷新时间,字符串 */ public static String getRefreshTimeByToken(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); return RedisUtils.hget(token, "refreshTime").toString(); } return null; } /** * 从Redis中获取用户名 * * @param token Token信息 * @return */ public static String getUserNameByToken(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); return RedisUtils.hget(token, "username").toString(); } return null; } /** * 从Redis中获取IP * * @param token Token信息 * @return */ public static String getIpByToken(String token) { if (StringUtils.isNotEmpty(token)) { // 去除JWT前缀 token = token.substring(JWTConfig.tokenPrefix.length()); return RedisUtils.hget(token, "ip").toString(); } return null; } }
  • 修改登录成功处理类,将Token添加到Redis中
Copy
import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import com.c3stones.security.entity.SysUserDetails; import com.c3stones.security.utils.JWTTokenUtils; import com.c3stones.utils.AccessAddressUtils; import com.c3stones.utils.ResponseUtils; import lombok.extern.slf4j.Slf4j; /** * 登录成功处理类 * * @author CL * */ @Slf4j @Component public class UserLoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal(); // 获得请求IP String ip = AccessAddressUtils.getIpAddress(request); sysUserDetails.setIp(ip); String token = JWTTokenUtils.createAccessToken(sysUserDetails); // 保存Token信息到Redis中 JWTTokenUtils.setTokenInfo(token, sysUserDetails.getUsername(), ip); log.info("用户{}登录成功,Token信息已保存到Redis", sysUserDetails.getUsername()); Map<String, String> tokenMap = new HashMap<>(); tokenMap.put("token", token); ResponseUtils.responseJson(response, ResponseUtils.response(200, "登录成功", tokenMap)); } }
  • 修改登出成功处理类,登出后将Token添加至黑名单
Copy
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import com.c3stones.security.config.JWTConfig; import com.c3stones.security.utils.JWTTokenUtils; import com.c3stones.utils.ResponseUtils; import lombok.extern.slf4j.Slf4j; /** * 登出成功处理类 * * @author CL * */ @Slf4j @Component public class UserLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { // 添加到黑名单 String token = request.getHeader(JWTConfig.tokenHeader); JWTTokenUtils.addBlackList(token); log.info("用户{}登出成功,Token信息已保存到Redis的黑名单中", JWTTokenUtils.getUserNameByToken(token)); SecurityContextHolder.clearContext(); ResponseUtils.responseJson(response, ResponseUtils.response(200, "登出成功", null)); } }
  • 修改JWT过滤器,对Token进行校验
      加入黑名单后将拦截;
      Token过期但在刷新期间内将刷新Token;
      超过过期时间且超过刷新时间,将拦截;
      请求IP与Token中IP不一致,将拦截。
Copy
import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import com.c3stones.security.config.JWTConfig; import com.c3stones.security.entity.SysUserDetails; import com.c3stones.security.utils.JWTTokenUtils; import com.c3stones.utils.AccessAddressUtils; import com.c3stones.utils.ResponseUtils; import lombok.extern.slf4j.Slf4j; /** * JWT权限过滤器,用于验证Token是否合法 * * @author CL * */ @Slf4j public class JWTAuthenticationFilter extends BasicAuthenticationFilter { public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { // 取出Token String token = request.getHeader(JWTConfig.tokenHeader); if (token != null && token.startsWith(JWTConfig.tokenPrefix)) { // 是否在黑名单中 if (JWTTokenUtils.isBlackList(token)) { ResponseUtils.responseJson(response, ResponseUtils.response(505, "Token已失效", "Token已进入黑名单")); return; } // 是否存在于Redis中 if (JWTTokenUtils.hasToken(token)) { String ip = AccessAddressUtils.getIpAddress(request); String expiration = JWTTokenUtils.getExpirationByToken(token); String username = JWTTokenUtils.getUserNameByToken(token); // 判断是否过期 if (JWTTokenUtils.isExpiration(expiration)) { // 加入黑名单 JWTTokenUtils.addBlackList(token); // 是否在刷新期内 String validTime = JWTTokenUtils.getRefreshTimeByToken(token); if (JWTTokenUtils.isValid(validTime)) { // 刷新Token,重新存入请求头 String newToke = JWTTokenUtils.refreshAccessToken(token); // 删除旧的Token,并保存新的Token JWTTokenUtils.deleteRedisToken(token); JWTTokenUtils.setTokenInfo(newToke, username, ip); response.setHeader(JWTConfig.tokenHeader, newToke); log.info("用户{}的Token已过期,但为超过刷新时间,已刷新", username); token = newToke; } else { log.info("用户{}的Token已过期且超过刷新时间,不予刷新", username); // 加入黑名单 JWTTokenUtils.addBlackList(token); ResponseUtils.responseJson(response, ResponseUtils.response(505, "Token已过期", "已超过刷新有效期")); return; } } SysUserDetails sysUserDetails = JWTTokenUtils.parseAccessToken(token); if (sysUserDetails != null) { // 校验IP if (!StringUtils.equals(ip, sysUserDetails.getIp())) { log.info("用户{}请求IP与Token中IP信息不一致", username); // 加入黑名单 JWTTokenUtils.addBlackList(token); ResponseUtils.responseJson(response, ResponseUtils.response(505, "Token已过期", "可能存在IP伪造风险")); return; } UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( sysUserDetails, sysUserDetails.getId(), sysUserDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); } }

4. 测试#

  • 用户登录
  • 查看Redis:
      配置文件中配置Token过期时间为300秒即5分钟,刷新时间为7天之内有效。
  • 超过有效时间但为超过刷新时间测试
      注意看响应头中Authorization值的变化。
  • 使用之前的Token再次请求
  • 在其他IP主机上访问接口,使用本机申请的Token
Copy
# 请求: curl -X POST -H "Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoidXNlciIsImlhdCI6MTU5NDc3NjYzMiwiaXNzIjoiQzNTdG9uZXMiLCJleHAiOjE1OTQ3NzY5MzIsImF1dGhvcml0aWVzIjoiW3tcImF1dGhvcml0eVwiOlwiUk9MRV9VU0VSXCJ9XSIsImlwIjoiMTI3LjAuMC4xIn0.ogo8Q3-MOj5bFLA01bxM1Fh0plaB8tvwwSnDlgHQqxVMm0zOiKvsQhsqO673xeFPMpSXhFhPu57coaHX1J5E0A" "http://192.168.0.100:8080/user/info" # 结果: {"code":505,"data":"可能存在IP伪造风险","msg":"Token已过期"}
  • 登出,使用响应头中的新的Token

      注意:每次测试后,请查看Redis中的变化。

5. 项目地址#

  spring-security-jwt-redis-demo

posted @   C3Stones  阅读(7541)  评论(4编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示
CONTENTS