JAVA后端开发—— JWT(JSON Web Token)实践

1. 什么是HTTP请求头 (Request Headers)?

当你的浏览器或手机App向服务器发起一个HTTP请求时,这个请求并不仅仅包含你要访问的URL(比如 /logout)和可能的数据(请求体),它还附带了一堆"元数据(Metadata) ",这些元数据就是请求头

请求头是一些键值对(Key-Value pairs),它们向服务器提供了关于本次请求的各种上下文信息,比如:

  • Host: api.example.com (我想访问哪个服务器)

  • User-Agent: Mozilla/5.0 ... (我是用什么浏览器或设备发起的请求)

  • Accept: application/json (我希望你返回给我JSON格式的数据)

  • Content-Type: application/json (如果我发送了数据,那么这些数据的格式是JSON)

  • Authorization : Bearer eyJhbGciOiJIUzI1Ni... (这是身份凭证)

2. 为什么需要 Authorization 请求头?

HTTP协议本身是无状态的(Stateless)。这意味着服务器默认情况下,每一次收到请求,都把它当作一个全新的、陌生的请求来对待。它不会记得你上一次是谁,或者你是否已经登录过。

为了解决这个问题,我们需要一种机制来让客户端在每次请求时都"自报家门"。Authorization 请求头就是目前最主流的实现方式,特别是配合 Token-based Authentication(基于令牌的认证)

Token认证机制具有安全性。

  • 核心凭证只传一次 : 用户的账号密码只在登录那一次通过HTTPS安全地传输到服务器。

  • 暴露的是"临时工" : Token是一个临时的、可控的 凭证。它本质上是服务器对"我信任这个客户端"的一次授权声明。即使Token在传输过程中被截获,它的危害也是有限的

    • 有时效性 : Token通常被设置为在几小时或几天后自动过期。攻击者拿到一个过期的Token是完全没用的。

    • 可被吊销: 服务器可以建立一个"黑名单"机制。一旦发现某个Token泄露,可以立刻将其加入黑名单,使其立即失效。

    • 权限可控 : Token的Payload(载荷)部分可以包含用户的角色和权限信息。服务器可以签发一个权限受限的Token(例如,一个只读Token),即使泄露,攻击者也无法执行写操作。

3. 典型的登录认证流程

  1. 登录: 用户提交用户名和密码到一个登录接口(如 /login)。

  2. 获取令牌 (Token) : 服务器验证用户名和密码成功后,生成一个加密的、有时效性的字符串 ,这个字符串就是令牌(Token)。服务器会将这个Token返回给客户端。

  3. 客户端存储令牌: 客户端(浏览器或App)收到Token后,会将其安全地存储起来(比如在浏览器的 localStorage 或 sessionStorage 中)。

  4. 后续请求携带令牌 : 从此以后,客户端向服务器发起的每一个需要认证的请求 ,都必须在 Authorization 请求头中附带上这个Token。通常,Token前面还会加上一个Bearer的前缀,表示"持有者认证"。

  5. 服务器验证令牌 : 服务器收到请求后,会先检查 Authorization 请求头。如果存在,它会取出Token,对其进行解密和验证(检查签名是否正确、是否过期等)。验证通过后,服务器就知道了"哦,原来是张三发来的请求",然后才继续处理业务逻辑。

4. Token的生命周期

  1. "生成"阶段 : 每一次独立的登录(Login)行为 ,都会触发服务器生成一个全新的、不一样的Token

  2. "使用"阶段 : 在某一次登录成功后 ,直到这个Token过期或用户手动登出之前,客户端在发起每一次业务请求(Request)时,都会重复使用这同一个Token

5.JWT的"三段式"结构

JWT Token而是由 三个部分 通过 . 连接而成的:

Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

  • 第一部分:Header (头部) eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

    • 内容: 描述这个JWT元信息的一个JSON对象,经过Base64Url编码而成。

    • 解码后: {"alg": "HS256", "typ": "JWT"}

    • 含义:

      • alg: "HS256" (HMAC using SHA-256),声明了签名部分(Signature)使用的加密算法

      • typ: "JWT",声明了这是一个JWT。

  • 第二部分:Payload (载荷) eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

    • Payload 的设计初衷是用来存放与"用户身份和权限"相关的、相对稳定的信息,这些信息在用户的一次登录会话中是不会改变的。

    • 内容 : 包含了我们想传递的实际业务数据的一个JSON对象,也经过Base64Url编码。

    • 解码后: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}

    • 含义 (这里是一些标准字段):

      • sub: Subject (主题),通常存放用户的唯一ID------ 这就是"判断这个Token是谁的"关键!

      • name: 用户名。

      • iat: Issued At (签发时间),是一个时间戳。

      • exp : Expiration Time (过期时间),是一个时间戳。------ 这就是"检查是否过期"的关键!

      • 注意 : 任何人都可以对Header和Payload进行Base64Url解码,看到里面的内容。所以,绝对不能在Payload中存放敏感信息,如密码!

  • 第三部分:Signature (签名) SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    • 内容 : 这是一个加密后的字符串,是JWT安全性的核心。

    • 生成过程 : 它是通过以下公式,使用在**服务器端秘密保存的一个密钥(Secret Key)**生成的:

      HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey )

    • 含义 : 这个签名就像是这份"声明文件"(Header + Payload)的"数字指纹 "或"防伪印章"。

6. 服务器验证Token的全过程

假设服务器收到一个HTTP请求,请求头里有 Authorization: Bearer [一个JWT字符串]。在若依这类框架中,通常会有一个过滤器(Filter),比如 JwtAuthenticationTokenFilter,它会在请求到达你的Controller之前,自动执行以下验证流程:

第1步:检查请求头,取出Token

这步很简单,就是代码层面的字符串操作。

第2步:解密和验证(核心步骤)

这一步通常会由一个专门的JWT工具类(比如使用 jjwt 库)来完成。validateToken(jwtToken) 方法内部会做以下事情:

a. 拆分Token

  • 将收到的 jwtToken 字符串,按 . 分割成三部分:headerPart, payloadPart, signaturePart。

b. 验证签名(Signature)------ 最关键的安全校验

  • 服务器端有一个只有自己知道的、绝对保密的 secretKey。这个密钥在项目启动时就加载到内存中了。

  • 服务器会完全忽略收到的 signaturePart。

  • 它会用收到的 headerPart 和 payloadPart ,以及自己保存在内存中的 secretKey重新计算一次签名

    newSignature = HMACSHA256(headerPart + "." + payloadPart, mySecretKey)

  • 然后,它将 newSignature 与收到的 signaturePart 进行逐位比较

    • 如果两者完全一致 : 这证明了这个Token确实是由我(或拥有相同密钥的其他可信服务)签发的,并且 Header 和 Payload 在传输过程中没有被任何人篡改过 。因为只有持有 secretKey 才能生成出正确的签名。------ 验证通过!

    • 如果两者不一致 : 这说明Token要么是伪造的,要么内容被篡改了。------ 验证失败,立即拒绝请求!

c. 验证载荷(Payload)------ 业务规则校验

  • 在签名验证通过后,服务器才会信任 Payload 里的内容。

  • 它会对 payloadPart 进行Base64Url解码,得到一个JSON对象。

  • 然后,它会检查Payload中的标准声明(Claims)

    • 检查过期时间 (exp): if (currentTime > payload.getExp()) { ... }

      • 获取exp字段的值(一个时间戳)。

      • 与当前服务器时间进行比较。

      • 如果当前时间已经超过了exp时间,说明Token已过期 。------ 验证失败,拒绝请求!

    • 还可以检查其他声明,比如nbf (Not Before,生效时间)等。

第3步:判断这个Token是谁的,并建立会话上下文

在签名和有效期都验证通过后,现在服务器可以完全信任这个Token了。

a. 提取用户信息

  • 服务器会从已经解码的 Payload 中,提取出用户的唯一标识 ,通常是 sub (Subject) 字段。

    String userId = payload.get("sub");

  • 它还可能提取出角色、权限等其他信息。

    List<String> roles = payload.get("roles");

b. 建立安全上下文 (Security Context)

  • 现在服务器知道了:"这个合法请求是用户 userId 发来的,他拥有 roles 这些角色。"

  • 框架(如Spring Security)会创建一个认证对象(Authentication),把这些用户信息封装进去。

  • 然后,将这个认证对象存入一个**与当前线程绑定的"安全上下文"**中(SecurityContextHolder)。

c. 放行请求

  • 过滤器的工作到此完成,它会将请求放行,继续传递给后续的过滤器,最终到达Controller方法。当请求最终到达Controller时,不再需要关心Token了。只需要从那个"安全上下文"中获取用户信息即可。
相关推荐
Linux编程用C3 分钟前
linux定时器使用
后端
jstart千语8 分钟前
【Spring AI】Advisors API—顾问(即拦截器)
java·人工智能·spring·ai
一碗绿豆汤27 分钟前
JAVA+AI教程-第三天
java·spring
gongzemin1 小时前
调用DeepSeek API实现DeepSeek网页版
前端·后端·next.js
典孝赢麻崩乐急1 小时前
Java学习 ------BIO模型
java·开发语言·学习
谢平康1 小时前
支持不限制大小,大文件分段批量上传功能(不受nginx /apache 上传大小限制)
java·vue.js·spring boot
期待のcode1 小时前
java内存图
java·开发语言
anan4661 小时前
开发中的接口调试难题?Pretender-Proxy 来帮你解决
java
MapleWan320631 小时前
Flask 搭建 Restful 风格项目扩展(Namespace、Swagger)
后端
MaxHua1 小时前
Git实用操作指南
后端