一、功能需求

最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 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

系统中 tokenrefreshToken 的传递一律放在请求头。

三、实现思路

1、生成 token 和 refreshToken

用户登录系统的时候,后台给用户生成 tokenrefreshToken 并放在响应头中返回

2、系统 判断 token 是否合法

  1. token 未失效的时的处理

  2. 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 自动刷新的更多相关文章

  1. spring oauth2+JWT后端自动刷新access_token

    这段时间在学习搭建基于spring boot的spring oauth2 和jwt整合. 说实话挺折腾的.使用jwt做用户鉴权,难点在于token的刷新和注销. 当然注销的难度更大,网上的一些方案也没 ...

  2. Spring Boot整合实战Spring Security JWT权限鉴权系统

    目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.像以前做项目的安全认证基于 session 的登录拦截,属于后端全栈式的开发的模式 ...

  3. Spring Security + JWT学习

    开胃:Oauth2认证流程分析 现在第三方登录已经很普遍了,随便哪个App都会有使用微信登录,使用手机号码登录,或者使用支付宝登录等功能... 下面我们就以使用微信登录,做一个简单的流程分析分析 开胃 ...

  4. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二)

    Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二) 摘要 上一篇https://javaymw.com/post/59我们已经实现了基本的登录和t ...

  5. Spring Security + JWT实现前后端分离权限认证

    现在国内前后端很多公司都在使用前后端分离的开发方式,虽然也有很多人并不赞同前后端分离,比如以下这篇博客就很有意思: https://www.aliyun.com/jiaocheng/650661.ht ...

  6. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)

    标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...

  7. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  8. 4-12 Spring Security + JWT

    Spring Security + JWT 此前,在处理登录的业务中,当视为登录成功时,返回的字符串并不是JWT数据,则应该将此数据改为必要的JWT数据. @Service public class ...

  9. spring boot + spring security +JWT令牌 +前后端分离--- 心得

    1.前言 观看这篇随笔需要有spring security基础. 心得: 1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定 2.生成的token是将登录通过的用户的权限拼接的字符 ...

随机推荐

  1. ARP原理和常见分类

    路由器一个重要功能是隔离广播域.就是不转发广播包,而ARP请求就是个广播包===> arp 代理 1. ARP交互流程: (1)目的地址和主机在同一个网段(A-C): A ----------- ...

  2. Sentry Web 前端监控 - 最佳实践(官方教程)

    系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...

  3. CUDA 矩阵乘法终极优化指南

    作者:马骏 | 旷视 MegEngine 架构师 前言 单精度矩阵乘法(SGEMM)几乎是每一位学习 CUDA 的同学绕不开的案例,这个经典的计算密集型案例可以很好地展示 GPU 编程中常用的优化技巧 ...

  4. CommonsCollections2 反序列化利用链分析

    在 ysoserial中 commons-collections2 是用的 PriorityQueue reaObject 作为反序列化的入口 那么就来看一下 java.util.PriorityQu ...

  5. 企业快速开发平台Spring Cloud+Spring Boot+Mybatis+ElementUI 实现前后端分离

    鸿鹄云架构一系统管理平台 鸿鹄云架构[系统管理平台]使用J2EE技术来实施,是一个大型分布式的面向服务的JavaEE体系快速研发平台,基于模块化.服务化.原子化.热部署的设计思想,使用成熟领先的无商业 ...

  6. type switch使用

    type    switchs用法 这里存在一个未知类型变量的内省操作(introspection operation),就是x.(type),其中x是interface{}类型

  7. Maven项目之间关系介绍

    Maven项目之间的关系 依赖关系 单纯的项目A中需要项目B中的资源,将项目B打成Jar包被A依赖,此时项目A直接调用项目B中资源即可. 项目A和项目B此时形成最基本的依赖关系. 继承关系 需要场景: ...

  8. 3.8学习总结——Android保存信息

    为了保存软件的设置参数,Android平台为我们提供了一个SharedPreferences接口,它是一个轻量级的存储类,特别适合用于保存软件配置参数.使用SharedPreferences保存数据, ...

  9. CPU到底是什么东西?它为什么能够执行数学运算?

    CPU到底是什么东西?它为什么能够执行数学运算? 本文地址http://yangjianyong.cn/?p=20转载无需经过作者本人授权 简单的物理电路 先来看一张初中学过的物理电路图: 从图中我们 ...

  10. express 路由匹配和数据获取

    express配置路由只需要通过app.method(url,func)来配置,其中url配置和其中的参数获取方法不同 直接写全路径 路由中允许存在. get请求传入的参数 router.get(&q ...