Spring Security Jwt Token 自动刷新
token的自动刷新
一、功能需求
最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 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
Spring Security Jwt Token 自动刷新的更多相关文章
- spring oauth2+JWT后端自动刷新access_token
这段时间在学习搭建基于spring boot的spring oauth2 和jwt整合. 说实话挺折腾的.使用jwt做用户鉴权,难点在于token的刷新和注销. 当然注销的难度更大,网上的一些方案也没 ...
- Spring Boot整合实战Spring Security JWT权限鉴权系统
目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.像以前做项目的安全认证基于 session 的登录拦截,属于后端全栈式的开发的模式 ...
- Spring Security + JWT学习
开胃:Oauth2认证流程分析 现在第三方登录已经很普遍了,随便哪个App都会有使用微信登录,使用手机号码登录,或者使用支付宝登录等功能... 下面我们就以使用微信登录,做一个简单的流程分析分析 开胃 ...
- Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二)
Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二) 摘要 上一篇https://javaymw.com/post/59我们已经实现了基本的登录和t ...
- Spring Security + JWT实现前后端分离权限认证
现在国内前后端很多公司都在使用前后端分离的开发方式,虽然也有很多人并不赞同前后端分离,比如以下这篇博客就很有意思: https://www.aliyun.com/jiaocheng/650661.ht ...
- Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)
标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...
- 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目
一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...
- 4-12 Spring Security + JWT
Spring Security + JWT 此前,在处理登录的业务中,当视为登录成功时,返回的字符串并不是JWT数据,则应该将此数据改为必要的JWT数据. @Service public class ...
- spring boot + spring security +JWT令牌 +前后端分离--- 心得
1.前言 观看这篇随笔需要有spring security基础. 心得: 1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定 2.生成的token是将登录通过的用户的权限拼接的字符 ...
随机推荐
- vue实现拖动div元素
html: <div id="app1"> <div v-drag class="drag"></div> <div ...
- 计算机基础知识以及java JDK、JRE
计算机 计算机(Computer)全称:电子计算机,是一种能够按照程序运行,自动.高速处理海量数据的现代化智能电子设备.由硬件和软件所组成,没有安装任何软件的计算机称为裸机.常见的形式有台式计算机.笔 ...
- Prism+WPF使用DependencyInjection实现AutoMapper的依赖注入功能
前言 在使用PRISM+WPF开发项目的过程中,需要使用AutoMapper实现对象-对象的映射功能.无奈PRISM没有相关对AutoMapper相关的类库,于是转换一下思想,在nuget 中存在有关 ...
- Python增强下git那长长的指令
场景 现如今有点规模的公司都使用GitFlow模式进行分支管理.虽然插件给我们带来了非常大的方便,但切换分支.找分支.起分支还是那么的麻烦 需求 在社会主次国家,每个生活在底层的劳动人民,他们默默的工 ...
- VS Code + WSL 搭建 RaspberryPi Pico 开发环境
前面老周写一堆 .NET 与树莓派相关的水文.其实使用的是.net的 IOT 库,并不只是树莓派,其他运行 Linux 的开发板都适用,只要有 GPIO 就行.老周好像在哪看到过,有 USB 转GPI ...
- 自己实现一个Controller——终极型
经过前两篇的学习与实操,也大致掌握了一个k8s资源的Controller写法了,如有不熟,可回顾 自己实现一个Controller--标准型 自己实现一个Controller--精简型 但是目前也只能 ...
- Java基础系列(18)- if选择结构
if单选择结构 我们很多时候需要去判断一个东西是否可行,然后我们才去执行,这样一个过程在程序中用if语句来表示 语法 if (布尔表达式){ //如果布尔表达式为True将执行的语句 } packag ...
- git 切换分支
# 查看git源 git remote -v git remote set-url origin http://mingzhanghui@xx.xx.xx.xx:8090/r/ENSO/weba ...
- display:flex;下的子元素width无效问题
因为flex属性默认值为flex:0 1 auto;其中 1 为 flex中的 flex-shrink 属性. 该属性介绍: 一个数字,规定项目将相对于其他灵活的项目进行收缩的量. 根据上述介绍可以理 ...
- web自动化:IE11运行Python+selenium程序
from selenium import webdriver # 运行此脚本前必须按要求修改注册表'''[HKEY_CURRENT_USER\Software\Microsoft\Internet E ...