JWT笔记

news/2025/11/14 22:47:00/文章来源:https://www.cnblogs.com/Elaina-/p/19214481

JWT笔记

一、简单介绍

JWT由三部分组成:
header,payload,Signature(头部,载荷,签证)

二、工具类

工具类是一个封装类,将一些对Jwt相关的操作进行整理,以便调用。

1. JwtToken创建

(不同版本,方法或有变动)调用构造方法:Jwts.builder();

该方法是链式调用,依次对jwt的三个组成部分进行配置。

  1. Header:
    头部主要声明类型和使用的加密算法(加密算法现在后自动添加,也可以主动声明)
  2. Payload:
    载荷由claims传入,.claims()方法接收Map类型,里面存入数据可自行定义(通常会存入用户名,id这些安全性要求不高的数据)
  3. Signature:
    签证由signWith()进行,参数为密钥和签名方法,该版本的密钥有所要求,需要符合一定要求(有些版本要求会低一些)
  4. 构建
    最后由.compact()进行构建,返回的token由根据 . 分隔的三部分组成。
    public String createToken(Map<String, Object> claims) {Date nowDate = new Date();Date expireDate = new Date(nowDate.getTime() + expire * 1000L);try {return Jwts.builder().header() // 设置头部.add("typ", "JWT") // 设置头部类型.and().claims(claims) // 自定义负载.issuedAt(nowDate).expiration(expireDate).signWith(getSigningKey(), Jwts.SIG.HS256)// 改为更安全的密钥处理方式SecretKey.compact();} catch (IllegalStateException e) {LOGGER.error("创建JWT失败: 签名密钥不可用", e);throw e;} catch (Exception e){LOGGER.error("创建JWT失败", e);throw new RuntimeException("创建 JWT 失败", e);}}

2. 密钥构建

Keys.hmacShaKeyFor()将字节数组包装成SecretKey,即被用于签名的密钥;(推荐使用至少 32 字节,不符合安全检查会抛出异常)

所以我们的私钥如果不是字节数组,要主动将其转换(调用Decoders.BASE64.decode())

    private SecretKey getSigningKey(){if(secret == null || secret.trim().isEmpty()){LOGGER.error("JWT密钥未配置,请在配置文件中设置jwt.secret");throw new IllegalStateException("JWT密钥未配置,请在配置文件中设置jwt.secret");}try{byte[] keyBytes = Decoders.BASE64.decode(secret);//解码为字节数组return Keys.hmacShaKeyFor(keyBytes);//生成密钥} catch(IllegalArgumentException e){try{LOGGER.info("BASE64解码失败,尝试使用原始字符串作为密钥");return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));//备用方案,直接使用字符串的字节数组(不推荐)}catch (Exception ex){LOGGER.error("JWT密钥无效,请检查jwt.secret的配置", ex);throw new IllegalStateException("JWT密钥无效,请检查jwt.secret的配置", ex);}}}

3. token解析

固定的链式调用:

  1. 调用Jwts.parser()开启配置解析器,
  2. 调用.verifyWith()配置签名认证使用的密钥
  3. .build()返回已配置的 JwtParser 实例
  4. .parseSignedClaims()根据解析器执行解析(返回Jws用于后续操作)
  5. .getPayload()获取负载
    // 解析 JWT token,获取负载payloadpublic Claims getTokenClaim(String token) {try {LOGGER.info("开始解析JWT:{}",token);return Jwts.parser().verifyWith(getSigningKey()) // 使用 verifyWith(SecretKey),替代已弃用的 setSigningKey.build().parseSignedClaims(token) // 使用 parseSignedClaims 方法解析签名的 JWT.getPayload(); // 获取负载部分} catch (IllegalStateException e) {LOGGER.error("解析JWT失败: 签名密钥不可用", e);throw e;}  catch (Exception e) {LOGGER.info("JWT格式验证失败:{}",token);return null;}}

4. 读取负载信息

以过期时间为例,从解析到的负载中调用相关方法即可(claims.getExpiration())

    public Date getExpirationDateFromToken(String token) {try {Claims claims = getTokenClaim(token);return claims != null ? claims.getExpiration() : null;} catch (Exception e) {LOGGER.info("获取Token过期时间失败:{}", token);return null;}}

三、于SpringSecurity集成

1. token过滤器

在SpringSecurity的验证过滤器前新增过滤器用于token验证(原本我认为是要重写验证,网上方案却多是新增过滤器)

@Component
public class JwtValidationFilter extends OncePerRequestFilter{private static final Logger LOGGER = LoggerFactory.getLogger(JwtValidationFilter.class);// 注入UserDetailsService,SecretKey,JwtUtilprivate final UserDetailsService userDetailsService;private final JwtUtil jwtUtil;@Value("${jwt.secret}")private String secretKey;@Autowiredpublic JwtValidationFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {this.userDetailsService = userDetailsService;this.jwtUtil = jwtUtil;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 0.获取请求tokenString token = request.getHeader("Authorization");// 1.检查请求头内Authorization信息try{// 1.1 如果没有,放行// 1.2 如果有,且不是以Bearer开头,放行if(StringTools.isBlank(token) || !token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)){filterChain.doFilter(request, response);return;}// 2.去除Bearer前缀token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());// 3.解析 Tokenboolean isValidDate = jwtUtil.isTokenExpired(token);// 4.检查 Token有效,无效捕获并则抛出异常if(!isValidDate){LOGGER.error("JwtValidationFilter error: token is invalid");throw new RuntimeException(ResultEnum.UNAUTHORIZED.getMessage());}// 4.1.有效,进行验证Claims payloads = jwtUtil.getTokenClaim(token);//获取负载String username = payloads.getSubject();//都读取负载上用户名UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(username);//根据用户名加载用户信息UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());//创建认证令牌authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));//设置请求详情SecurityContextHolder.getContext().setAuthentication(authenticationToken);//将认证令牌存入安全上下文} catch (Exception e) {// 4.2.无效,清除上下文并抛出异常LOGGER.error("JwtValidationFilter error: {}", e.getMessage());SecurityContextHolder.clearContext();}// 5.继续执行过滤链filterChain.doFilter(request,response);}
}

2. 配置SecurityConfig

在配置文件中设置token的过滤器

@Configuration
public class SecurityConfig {private final UserDetailsService userDetailsService;private final JwtUtil jwtUtil;@Autowiredpublic SecurityConfig(UserDetailsService userDetailsService, JwtUtil jwtUtil) {this.userDetailsService = userDetailsService;this.jwtUtil = jwtUtil;}@Beanpublic PasswordEncoder passwordEncoder() {// 也可用有参构造,取值范围是 4 到 31,默认值为 10。数值越大,加密计算越复杂return new BCryptPasswordEncoder();	}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}//过滤链配置@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception{http.authorizeHttpRequests(authorize -> authorize //开启授权.requestMatchers("/login","/register","/api/register", "/register.html", "/api/login") //放行请求.permitAll() //允许所有人访问.anyRequest() //允许所有请求.authenticated() //认证后访问自动授权).formLogin(Customizer.withDefaults()) //使用默认的登陆登出页面进行授权登陆.rememberMe(Customizer.withDefaults()); // 启用“记住我”功能http.csrf(csrf -> csrf.disable()); //关闭csrfhttp.addFilterBefore(new JwtValidationFilter(userDetailsService, jwtUtil), UsernamePasswordAuthenticationFilter.class); //添加JWT过滤器return http.build();}
}

3. 登录生成token

现在有了验证流程,生成token的流程还没有加入。

  1. 在登录模块调用token的生成方法
  2. 将生成的token封装进dto中(封装的数据结构)
    @Overridepublic LoginDto login(String username, String password) {//TODO 0.准备工作,注入AuthenticationManager、SecurityPropertiesLoginDto loginDto = new LoginDto();String token;Long expiration;try{//TODO 1.调用认证方法Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));SecurityContextHolder.getContext().setAuthentication(authentication);//TODO 2.生成JWTtoken = jwtUtil.createToken(authentication);loginDto.setToken(token);}catch (Exception e){LOGGER.error("登录异常:{}", e.getMessage());throw new ApiException(e.getMessage());}//TODO 3.返回封装信息return loginDto;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/965752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025藤校申请少走弯路!5家权威机构测评,从申请到就业一站式护航

2025藤校申请少走弯路!5家权威机构测评,从申请到就业一站式护航随着全球顶尖教育资源竞争的加剧,藤校申请已进入"精准规划+实力赋能"的新阶段。专业的藤校申请留学机构不仅能精准匹配院校录取偏好,深度挖…

词向量:开启自然语言处理的奇妙之旅 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

11.14日学习笔记

类图设计 text +------------------------+ | Computer | +------------------------+ | -memory: Memory | | -cpu: CPU | | -hardDisk: HardDisk | | -os: OS …

[Python刷题记录]-有效的括号-栈-简单

[Python刷题记录]-有效的括号-栈-简单链接:20. 有效的括号 - 力扣(LeetCode) python中list就可以直接当栈用1 class Solution(object):2 def isValid(self, s):3 """4 :type s:…

[KaibaMath]1021 关于[0, 1]与[a, b]等势的证明

两个集合等势(也称基数相等),当且仅当存在从一个集合到另一个集合的双射(既是单射又是满射的映射,即一一映射)。下面给出[0, 1]与[a, b]等势的证明。

超参数调优:Grid Search 和 Random Search 的实战对比

模型训练完能够到达85%的准确率,很多人觉得就差不多了。但是通过超参数优化能让模型释放真正的潜力。最后那3-5个点的提升,往往决定了你的模型是"还行"还是"能打"。这篇文章会把Grid Search和Ra…

征程 6X 常见 kernel panic 问题

1. 概述 kernel panic 包含了多种内核异常类型,包括但不限于:空指针/异常指针、HungTask、RCU Stall、softlockup、hardlockup、OOM、BUG_ON。 下图是各种类型 panic 的路径:2. 通用方法kpanic 类异常均为 kernel 软…

实用指南:26秋招三分钟快速背诵一个八股-Spring中Bean的生命周期

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

11.14 —— (VP)2024icpc杭州

沈阳站赛前的最后一把 \(VP\) ,希望能在沈阳站偷到牌子qwq。。。 赛时 \(4\) 题,高罚时,输在 \(E\) 题开出来得太慢了。 \(A,K\) 纯签到。 \(E\): 显然所在楼层越高越有利,那么当前只要我们有免费上升的机会,就可…

2025-11-13~14 hetao1733837的刷题记录

2025-11-13~14 hetao1733837的刷题记录2025-11-13~14 hetao1733837的刷题记录 11-13 [JOISC 2014]Water Bottle 原题链接1:[P14422 [JOISC 2014] 水桶 / Water Bottle]([P14422 JOISC 2014] 水桶 / Water Bottle - 洛…

CF1381D The Majestic Brown Tree Snake/SS251114C. 历遍的树(inverse)

题意:给定一棵$n$个点的树,一条蛇在路径$(h,t)$ 上($h \neq t$),蛇类似火车移动,问蛇能否走到路径$(t,h)$ ,需线性或接近线性做法。思路:合法枢纽(关键点)指存在三条长度大于等于蛇长岔路的点。先证明若直径…

2025年11月宁夏数字人服务商/供应商最新专业推荐:AI驱动下的企业数字化转型新引擎

随着人工智能技术的飞速发展,数字人作为AI技术的重要应用成果,正逐渐成为企业数字化转型的核心力量。在宁夏地区,多家科技企业纷纷布局数字人领域,为不同行业提供多样化的数字人解决方案。本榜单基于技术实力、产品…

[KaibaMath]1020 与海明码有关的最小值问题

海明码的核心原理是通过插入校验位建立数据位与校验位的对应关系,实现单比特错误纠正和多比特错误检测。本文给出已知m个数据位,求校验位n的最小值的求解公式。

FreeSWITCH使用RNNoise进行实时通话降噪

FreeSWITCH使用RNNoise进行实时通话降噪操作系统:Debian 12.5_x64 FreeSWITCH版本: 1.10.11 rnnoise版本:0.2从事FreeSWITCH相关工作,大概率会遇到静音检测和降噪的事情,之前整理过vad相关的内容: https://mp.we…

不是 DE_aemmprty 的草稿纸

关于难度\(\textbf{Easy}\):我是 zak,我一眼秒了,我觉得这题没啥技巧啊! \(\textbf{Medium}\):完全自己想出,但想了较长时间。 \(\textbf{Hard}\):不完全是自己想出。\([0, 1]\) 表示在同档题中的难度。 qoj122…

2025年宁夏数字人服务商专业评测:AI驱动下的企业数字化转型新引擎

随着人工智能技术的飞速发展,数字人作为AI技术的重要应用成果,正逐渐成为企业数字化转型的核心力量。在宁夏地区,多家科技企业纷纷布局数字人领域,为不同行业提供多样化的数字人解决方案。本榜单基于技术实力、产品…

SqlServer资料

SqlServer资料SQLServer2014性能提升 SQLServer文件与文件组总结 SQL Server表分区详细版 SqlServer内存资源释放 SqlServer内存会话连接 SqlServer连接池 SQLServer触发器 SqlServer并行基础用…

如何将 Android 联系人备份到 Mac 的 4 种容易

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …