SpringSecurity Jwt Token 自动刷新的实现
作者:huan1993 发布时间:2022-04-28 18:49:45
功能需求
最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 token ,后续访问系统的请求都需要带上这个 token ,如果请求没有带上这个 token 或者 token 过期了,那么禁止访问系统。如果用户一直访问系统,那么还需要自动延长 token 的过期时间。
功能分析
1、token 的生成
使用现在比较流行的 jwt
来生成。
2、token 的自动延长
要实现 token 的自动延长,系统给用户 颁发 一个 token 无法实现,那么通过变通一个,给用户生成 2个 token ,一个用于 api 访问的 token ,一个 用于在 token 过期的时候 用来 刷新 的 refreshToken。并且 refreshToken 的 生命周期要比 token 的生命周期长。
3、系统资源的保护
可以使用Spring Security
来保护系统的各种资源。
4、用户如何传递 token
系统中 token
和 refreshToken
的传递一律放在请求头。
实现思路
1、生成 token 和 refreshToken
用户登录系统的时候,后台给用户生成 token
和 refreshToken
并放在响应头中返回
2、系统 判断 token 是否合法
token
未失效的时的处理token
失效 ,如何使用refreshToken
来生成新的token
核心代码如下
1、过滤器代码,token判断和再次生成
package com.huan.study.security.token;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huan.study.security.configuration.TokenProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author huan 2020-06-07 - 14:34
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class TokenAuthenticateFilter extends OncePerRequestFilter {
private final TokenProperties tokenProperties;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取 认证头
String authorizationHeader = request.getHeader(tokenProperties.getAuthorizationHeaderName());
if (!checkIsTokenAuthorizationHeader(authorizationHeader)) {
log.debug("获取到认证头Authorization的值:[{}]但不是我们系统中登录后签发的。", authorizationHeader);
filterChain.doFilter(request, response);
return;
}
// 获取到真实的token
String realToken = getRealAuthorizationToken(authorizationHeader);
// 解析 jwt token
Jws<Claims> jws = JwtUtils.parserAuthenticateToken(realToken, tokenProperties.getSecretKey());
// token 不合法
if (null == jws) {
writeJson(response, "认证token不合法");
return;
}
// token 是否过期
if (JwtUtils.isJwtExpired(jws)) {
// 处理过期
handleTokenExpired(response, request, filterChain);
return;
}
// 构建认证对象
JwtUtils.buildAuthentication(jws, tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/**
* 处理token过期情况
*
* @param response
* @param request
* @param filterChain
* @return
* @throws IOException
*/
private void handleTokenExpired(HttpServletResponse response, HttpServletRequest request, FilterChain filterChain) throws IOException, ServletException {
// 获取刷新 token
String refreshTokenHeader = request.getHeader(tokenProperties.getRefreshHeaderName());
// 检测 refresh-token 是否是我们系统中签发的
if (!checkIsTokenAuthorizationHeader(refreshTokenHeader)) {
log.debug("获取到刷新认证头:[{}]的值:[{}]但不是我们系统中登录后签发的。", tokenProperties.getRefreshHeaderName(), refreshTokenHeader);
writeJson(response, "token过期了,refresh token 不是我们系统签发的");
return;
}
// 解析 refresh-token
Jws<Claims> refreshToken = JwtUtils.parserAuthenticateToken(getRealAuthorizationToken(refreshTokenHeader),
tokenProperties.getSecretKey());
// 判断 refresh-token 是否不合法
if (null == refreshToken) {
writeJson(response, "refresh token不合法");
return;
}
// 判断 refresh-token 是否过期
if (JwtUtils.isJwtExpired(refreshToken)) {
writeJson(response, "refresh token 过期了");
return;
}
// 重新签发 token
String newToken = JwtUtils.generatorJwtToken(
refreshToken.getBody().get(tokenProperties.getUserId()),
tokenProperties.getUserId(),
tokenProperties.getTokenExpireSecond(),
tokenProperties.getSecretKey()
);
response.addHeader(tokenProperties.getAuthorizationHeaderName(), newToken);
// 构建认证对象
JwtUtils.buildAuthentication(JwtUtils.parserAuthenticateToken(newToken, tokenProperties.getSecretKey()), tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/**
* 写 json 数据给前端
*
* @param response
* @throws IOException
*/
private void writeJson(HttpServletResponse response, String msg) throws IOException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, String> params = new HashMap<>(4);
params.put("msg", msg);
response.getWriter().print(OBJECT_MAPPER.writeValueAsString(params));
}
/**
* 获取到真实的 token 串
*
* @param authorizationToken
* @return
*/
private String getRealAuthorizationToken(String authorizationToken) {
return StringUtils.substring(authorizationToken, tokenProperties.getTokenHeaderPrefix().length()).trim();
}
/**
* 判断是否是系统中登录后签发的token
*
* @param authorizationHeader
* @return
*/
private boolean checkIsTokenAuthorizationHeader(String authorizationHeader) {
if (StringUtils.isBlank(authorizationHeader)) {
return false;
}
if (!StringUtils.startsWith(authorizationHeader, tokenProperties.getTokenHeaderPrefix())) {
return false;
}
return true;
}
}
2、jwt 工具类代码
package com.huan.study.security.token;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultJws;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
/**
* jwt 工具类
*
* @author huan
* @date 2020-05-20 - 17:09
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JwtUtils {
/**
* 解析 jwt token
*
* @param token 需要解析的json
* @param secretKey 密钥
* @return
*/
public static Jws<Claims> parserAuthenticateToken(String token, String secretKey) {
try {
final Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return claimsJws;
} catch (ExpiredJwtException e) {
return new DefaultJws<>(null, e.getClaims(), "");
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException | IncorrectClaimException e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 判断 jwt 是否过期
*
* @param jws
* @return true:过期 false:没过期
*/
public static boolean isJwtExpired(Jws<Claims> jws) {
return jws.getBody().getExpiration().before(new Date());
}
/**
* 构建认证过的认证对象
*/
public static Authentication buildAuthentication(Jws<Claims> jws, String userIdFieldName) {
Object userId = jws.getBody().get(userIdFieldName);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userId, null, new ArrayList<>(0));
SecurityContextHolder.getContext().setAuthentication(testingAuthenticationToken);
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 生成 jwt token
*/
public static String generatorJwtToken(Object loginUserId, String userIdFieldName, Long expireSecond, String secretKey) {
Date expireTime = Date.from(LocalDateTime.now().plusSeconds(expireSecond).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setIssuedAt(new Date())
.setExpiration(expireTime)
.claim(userIdFieldName, loginUserId)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
}
完整代码
代码 https://gitee.com/huan1993/Spring-Security/tree/master/spring-security-jwt
来源:https://segmentfault.com/a/1190000022863757


猜你喜欢
- 一、定义实体类Person,封装生成的数据package net.dc.test;public class Person { private
- 获取map的key和value的方法分为两种形式:map.keySet():先获取map的key,然后根据key获取对应的value;map
- 相信对于一名JAVA开发者开说properties文件一定再熟悉不过了,比如一下配置:config.properties会经常存放一些系统常
- 本文为大家分享了maven环境变量配置的具体方法,供大家参考,具体内容如下一、maven环境配置1.解压apache-maven
- 员工管理系统要求如下:通过面向对象的编程思想,实现员工信息的增删改查,存储结构为数组。限定数组长度为100。实现页面:添加员工查找员工修改员
- 介绍本文实现的功能有:1、播放音乐2、自定义流星数量、飞行速度、光晕大小、流星大小3、自定义表白话语 运用到的知识点有:GUI:j
- 在request中可以获取到来自Http请求的body数据比如获取json格式数据代码:import com.alibaba.dubbo.c
- 一、Thread 的常见构造方法方法说明Thread()创建线程对象Thread(Runnable target)使用 Runnable 对
- spring boot2.x已经出来好一阵了,而且spring cloud 的最新Release版本Finchley.RELEASE,默认集
- 代理OkHttp 支持设置代理,使用OkHttpClient.proxy()即可设置。什么是代理?根据代理的对象不同,可分为正向代理和反向代
- 下面是android SDK中API中的主要java包的功能简介: android.app :提供高层的程序模型、提供基本的运行环
- 一、Hash加密,使用HashAlgorithm哈希算法类的派生类(MD5、SHA1等)特点:只能加密,不可逆。可对目标信息生成一段特定长度
- 作为.NET进阶内容的一部分,垃圾回收器(简称GC)是必须了解的内容。本着“通俗易懂”的原则,本文将解释CLR中垃圾回收器的工作原理。基础知
- 由于工作原因,需要将工程传递给一些新人小朋友,交互过程中,发现有几个典型的问题值得记录分享一下,以资来者。Androdi Studio 导入
- 1、 尽量指定类的final修饰符 带有final修饰符的类是不可派生的。 如果指定一个类为final,则该类所有的方法都是final。Ja
- 本文讲述了Android应用程序模型之应用程序,任务,进程,线程。分享给大家供大家参考,具体如下:大多数操作系统,在应用程序所寄存的可执行程
- 单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如
- 本文实例为大家分享了Android自定义StepView配送进度展示的具体代码,供大家参考,具体内容如下效果图使用可在layout文件下设置
- 概述关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的。也就是说,既
- 先给大家展示下效果图:不知道大家对效果图感觉怎么样,个人觉还不错,感兴趣的朋友可以参考下实现代码哦。public class ToggleB