当前位置: 首页 > 编程笔记 >

SpringSecurity Jwt Token 自动刷新的实现

应煌
2023-03-14
本文向大家介绍SpringSecurity Jwt Token 自动刷新的实现,包括了SpringSecurity Jwt 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

到此这篇关于SpringSecurity Jwt Token 自动刷新的实现的文章就介绍到这了,更多相关SpringSecurity 自动刷新内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!

 类似资料:
  • 想象一下,如果要直播比赛的比分,或股票市场的实时状态,或当前的外汇配给,该怎么实现呢?显然,要实现这种实时功能,您就不得不规律性地刷新页面。 JSP提供了一种机制来使这种工作变得简单,它能够定时地自动刷新页面。 刷新一个页面最简单的方式就是使用response对象的setIntHeader()方法。这个方法的签名如下: public void setIntHeader(String header

  • When webpack-dev-server is running with Automatic browser refresh the CSS will also update, but a bit differently. When you do a change to a CSS file the style tag belonging to that file will be updat

  • 本文向大家介绍webpack实现热加载自动刷新的方法,包括了webpack实现热加载自动刷新的方法的使用技巧和注意事项,需要的朋友参考一下 本文介绍了webpack实现热加载自动刷新的方法,分享给大家,具体如下: 一、webpack-dev-server 一个轻量级的服务 功能:修改代码及时呈现到浏览器上。 第一步:安装   第二步:写入到依赖 第三步:修改webpack配置文件 说明:如果想指定

  • 本文向大家介绍webpack结合express实现自动刷新的方法,包括了webpack结合express实现自动刷新的方法的使用技巧和注意事项,需要的朋友参考一下 前言 在我们开发的过程中,我们会使用webpack-dev-server实现自动刷新,webpack-dev-server会把编译后的文件全部保存在内存里,而不会写入到文件目录内。但当我们的开发是前端和后端在一个项目里的时候就不行了。我

  • 假设有一个网页,它是显示现场比赛成绩或股票市场状况或货币兑换率。对于所有这些类型的页面,您需要定期刷新网页。 Java Servlet 提供了一个机制,使得网页会在给定的时间间隔自动刷新。 刷新网页的最简单的方式是使用响应对象的方法 setIntHeader()。以下是这种方法的定义: public void setIntHeader(String header, int headerValue

  • When webpack-dev-server is running it will watch your files for changes. When that happens it rebundles your project and notifies browsers listening to refresh. To trigger this behavior you need to ch

  • 本文向大家介绍招聘网站基于jQuery实现自动刷新简历,包括了招聘网站基于jQuery实现自动刷新简历的使用技巧和注意事项,需要的朋友参考一下 将代码中res_id_encode参数的值,如5830549229E3096684665改成自己猎聘账户相关的ID即可。如何查看自己的ID,只要打开F12,点一下猎聘网个人中心的刷新简历,在network里面就能看到类似代码中的get请求了,把该请求的参数

  • 问题内容: 我正在使用$ _SESSION变量通过AJAX发送电子邮件(它们需要在不刷新页面的情况下发送),但是$ _SESSION变量不会自动更新,因此当它更改时,我需要刷新页面以更新变量。 是否可以不刷新而更新$ _SESSION变量? 这是我用来发送电子邮件的代码: 因此,基本上,如果$ _SESSION变量更改,则需要此AJAX电子邮件来识别它,而无需刷新。 谢谢你的帮助 问题答案: 当您