JWT详解

JSON Web Token(JWT)

一、JWT基础概念

1.1 JWT的定义

JSON Web Token(JWT)是一种基于JSON的开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。这种信息可以被验证和信任,因为它是经过数字签名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

在JWT出现之前,Web应用中常用的身份验证方式存在诸多局限性。例如,基于Session的认证需要服务器存储会话信息,在分布式系统中需要共享会话数据,这不仅增加了服务器的存储负担,也降低了系统的可扩展性。而JWT的出现则很好地解决了这些问题,它通过在客户端存储经过签名的令牌,实现了无状态的认证机制,使得服务器不需要存储任何会话信息,从而提高了系统的可扩展性和性能。

JWT的设计初衷是为了在网络应用环境中提供一种紧凑且自包含的方式,用于在各方之间传递声明。它可以在身份提供者和服务提供者之间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以用于分布式系统中传递一些非敏感的信息。

1.2 JWT的优势
1.2.1 无状态性

JWT最大的优势之一就是无状态性。在基于Session的认证中,服务器需要保存每个用户的Session信息,这使得服务器的状态变得复杂,尤其是在分布式系统中,需要通过共享Session存储(如Redis)来实现Session的同步,增加了系统的复杂度和维护成本。

而JWT将所有必要的用户信息都包含在令牌本身中,服务器不需要存储任何关于用户的状态信息。当客户端发送请求时,只需携带JWT,服务器通过验证令牌的签名即可确认用户的身份和权限。这种无状态的特性使得服务器可以更轻松地进行水平扩展,因为不需要在多个服务器之间同步会话数据。

例如,在一个大型的电商平台中,用户的访问量可能非常大,使用JWT进行认证可以让新增的服务器节点无需关注之前的用户会话状态,直接通过验证令牌来处理请求,大大提高了系统的扩展能力。

1.2.2 自包含性

JWT是自包含的,它包含了所有必要的用户声明信息,如用户ID、角色、权限等。这意味着服务器在验证令牌后,不需要再查询数据库或其他服务来获取用户的相关信息,从而减少了服务器的查询操作,提高了系统的响应速度。

比如,当用户请求访问某个需要特定权限的接口时,JWT中已经包含了该用户的权限信息,服务器只需解析令牌即可判断用户是否有权限访问该接口,而无需再去数据库中查询用户的权限表,节省了大量的数据库交互时间。

1.2.3 跨域支持

在现代Web应用中,跨域资源共享(CORS)是一个常见的需求。JWT可以轻松地在不同的域之间传递,因为它不需要依赖Cookie,而Cookie在跨域场景下存在诸多限制(如同源策略)。

例如,一个前端应用部署在域名A,后端API部署在域名B,前端应用可以通过在请求头中携带JWT的方式来访问后端API,而无需担心跨域带来的认证问题。这使得前后端分离的架构更加容易实现,也方便了不同服务之间的集成。

1.2.4 紧凑性

JWT的体积相对较小,这使得它可以通过URL、Cookie、HTTP头等方式轻松传递。由于其紧凑的特性,在网络传输过程中所占用的带宽较少,提高了数据传输的效率。

例如,在移动应用中,网络带宽可能相对有限,使用JWT可以减少数据传输量,提高应用的响应速度,改善用户体验。

1.3 JWT与其他认证方式的对比
1.3.1 JWT与Session认证
特性JWTSession认证
状态存储客户端存储令牌,服务器无状态服务器存储Session信息
扩展性易于水平扩展,无需共享状态需共享Session存储,扩展性差
跨域支持支持跨域,不依赖Cookie依赖Cookie,跨域支持差
性能验证速度快,无需查询数据库可能需要查询Session存储,性能略低
安全性令牌经过签名,可防止篡改,但令牌本身可能被窃取Session ID可能被窃取,需依赖Cookie安全机制

从上述对比可以看出,JWT在扩展性、跨域支持等方面具有明显优势,适用于分布式系统和前后端分离的架构;而Session认证在实现简单性上有一定优势,适用于小型、集中式的应用。

1.3.2 JWT与OAuth2.0

OAuth2.0是一个授权框架,用于授权第三方应用访问用户在资源服务器上的资源,而JWT是一种用于在各方之间安全传输信息的令牌格式。两者的应用场景和功能有所不同,但可以结合使用。

在OAuth2.0中,授权服务器可以使用JWT作为访问令牌(Access Token),因为JWT具有自包含、可验证等特性,非常适合作为访问令牌在客户端和资源服务器之间传递。例如,当用户通过OAuth2.0授权第三方应用访问其在某个平台上的资源时,授权服务器可以颁发一个JWT格式的访问令牌,第三方应用使用该令牌来访问资源服务器,资源服务器通过验证JWT的签名来确认令牌的有效性和权限。

需要注意的是,OAuth2.0是一个授权框架,而JWT是一种令牌格式,两者并不冲突,而是可以相互配合,实现更安全、高效的授权和认证机制。

1.3.3 JWT与SAML

SAML(Security Assertion Markup Language)是一种基于XML的开放标准,用于在不同的安全域之间交换身份认证和授权数据。与JWT相比,SAML具有以下不同点:

  • 数据格式:SAML使用XML格式,而JWT使用JSON格式。JSON比XML更轻量、解析速度更快,更适合在Web应用和移动应用中使用。
  • 适用场景:SAML主要用于企业级应用之间的单点登录(SSO),而JWT的适用场景更广泛,包括API认证、移动应用认证等。
  • 复杂度:SAML的实现相对复杂,而JWT的实现相对简单,易于理解和使用。

在选择JWT还是SAML时,需要根据具体的应用场景和需求来决定。对于简单的Web应用和移动应用,JWT通常是更好的选择;而对于复杂的企业级SSO场景,SAML可能更适合。

二、JWT的结构

2.1 JWT的整体结构

JWT由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature)。这三部分之间用点(.)分隔,形成一个完整的字符串,格式如下:

Header.Payload.Signature

例如,一个典型的JWT令牌可能如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkiLCJuYW1lIjoiSm9obiBEb2UifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

下面将详细解析这三部分的结构和作用。

2.2 头部(Header)

头部通常由两部分信息组成:令牌类型(typ)和所使用的签名算法(alg)。

  • typ:表示令牌的类型,对于JWT来说,其值通常为“JWT”。
  • alg:表示使用的签名算法,常见的有HMAC SHA256(HS256)、RSA(RS256)等。

头部的JSON格式如下:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后,这个JSON对象会被Base64Url编码,形成JWT的第一部分。Base64Url编码是Base64编码的一种变体,它将Base64中的“+”替换为“-”,“/”替换为“_”,并去除了末尾的填充字符“=”,以适应在URL中传输。

在Java中,可以使用相关的库来对头部进行Base64Url编码。例如,使用java.util.Base64类:

import java.util.Base64;
import com.google.gson.Gson;

public class JwtHeaderExample {
public static void main(String[] args) {
// 创建头部信息
Header header = new Header();
header.setAlg(“HS256”);
header.setTyp(“JWT”);

    <span class="token comment">// 转换为JSON字符串</span>
    <span class="token class-name">Gson</span> gson <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Gson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> headerJson <span class="token operator">&#61;</span> gson<span class="token punctuation">.</span><span class="token function">toJson</span><span class="token punctuation">(</span>header<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 进行Base64Url编码</span>
    <span class="token class-name">String</span> encodedHeader <span class="token operator">&#61;</span> <span class="token class-name">Base64</span><span class="token punctuation">.</span><span class="token function">getUrlEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withoutPadding</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encodeToString</span><span class="token punctuation">(</span>headerJson<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Encoded Header: &#34;</span> <span class="token operator">&#43;</span> encodedHeader<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Header</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> alg<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> typ<span class="token punctuation">;</span>
    
    <span class="token comment">// getter和setter方法</span>
    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getAlg</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> alg<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setAlg</span><span class="token punctuation">(</span><span class="token class-name">String</span> alg<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>alg <span class="token operator">&#61;</span> alg<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getTyp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> typ<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setTyp</span><span class="token punctuation">(</span><span class="token class-name">String</span> typ<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>typ <span class="token operator">&#61;</span> typ<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

上述代码中,首先创建了一个Header对象,设置了算法和类型,然后使用Gson库将其转换为JSON字符串,最后使用Base64.getUrlEncoder().withoutPadding()进行Base64Url编码,得到头部的编码结果。

2.3 载荷(Payload)

载荷是JWT的第二部分,包含了一些声明(Claims)。声明是关于实体(通常是用户)和其他数据的声明。JWT定义了三种类型的声明:注册声明、公共声明和私有声明。

2.3.1 注册声明

注册声明是JWT标准中定义的一些可选但推荐使用的声明,它们具有特定的含义,以下是一些常见的注册声明:

  • iss(Issuer):签发者,即生成JWT的主体,可以是一个URL或其他标识符。
  • sub(Subject):主题,即JWT所面向的用户或实体。
  • aud(Audience):受众,即JWT的接收者,通常是一个或多个URL,接收者需要验证自己是否在受众列表中。
  • exp(Expiration Time):过期时间,是一个Unix时间戳,表示JWT的过期时间,过期后的JWT将不再有效。
  • nbf(Not Before):生效时间,是一个Unix时间戳,表示在该时间之前,JWT是无效的。
  • iat(Issued At):签发时间,是一个Unix时间戳,表示JWT的生成时间。
  • jti(JWT ID):JWT的唯一标识符,用于防止JWT被重放,通常在一次性使用的场景中使用。

这些注册声明都是可选的,在实际使用中可以根据需要选择是否包含。

2.3.2 公共声明

公共声明是由用户自定义的声明,但为了避免冲突,这些声明应该在IANA JSON Web Token Registry中注册,或者使用包含命名空间的URI作为声明的名称。例如,可以定义一个“role”声明来表示用户的角色。

2.3.3 私有声明

私有声明是由通信双方约定的声明,用于传递一些自定义的信息,这些声明不会被JWT标准所定义,也不需要注册。例如,用户的姓名、邮箱等信息可以作为私有声明包含在载荷中。

载荷的JSON格式示例如下:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "admin"
}

同样,载荷也会被Base64Url编码,形成JWT的第二部分。

以下是在Java中对载荷进行编码的示例代码:

import java.util.Base64;
import java.util.Date;
import com.google.gson.Gson;

public class JwtPayloadExample {
public static void main(String[] args) {
// 计算签发时间和过期时间(1小时后过期)
long now = System.currentTimeMillis() / 1000;
long expirationTime = now + 3600;

    <span class="token comment">// 创建载荷信息</span>
    <span class="token class-name">Payload</span> payload <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Payload</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    payload<span class="token punctuation">.</span><span class="token function">setSub</span><span class="token punctuation">(</span><span class="token string">&#34;1234567890&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    payload<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">&#34;John Doe&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    payload<span class="token punctuation">.</span><span class="token function">setIat</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span><span class="token punctuation">;</span>
    payload<span class="token punctuation">.</span><span class="token function">setExp</span><span class="token punctuation">(</span>expirationTime<span class="token punctuation">)</span><span class="token punctuation">;</span>
    payload<span class="token punctuation">.</span><span class="token function">setRole</span><span class="token punctuation">(</span><span class="token string">&#34;admin&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 转换为JSON字符串</span>
    <span class="token class-name">Gson</span> gson <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Gson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> payloadJson <span class="token operator">&#61;</span> gson<span class="token punctuation">.</span><span class="token function">toJson</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 进行Base64Url编码</span>
    <span class="token class-name">String</span> encodedPayload <span class="token operator">&#61;</span> <span class="token class-name">Base64</span><span class="token punctuation">.</span><span class="token function">getUrlEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withoutPadding</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encodeToString</span><span class="token punctuation">(</span>payloadJson<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Encoded Payload: &#34;</span> <span class="token operator">&#43;</span> encodedPayload<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Payload</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> sub<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> name<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">long</span> iat<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">long</span> exp<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> role<span class="token punctuation">;</span>
    
    <span class="token comment">// getter和setter方法</span>
    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getSub</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> sub<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setSub</span><span class="token punctuation">(</span><span class="token class-name">String</span> sub<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>sub <span class="token operator">&#61;</span> sub<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> name<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setName</span><span class="token punctuation">(</span><span class="token class-name">String</span> name<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">&#61;</span> name<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">long</span> <span class="token function">getIat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> iat<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setIat</span><span class="token punctuation">(</span><span class="token keyword">long</span> iat<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>iat <span class="token operator">&#61;</span> iat<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">long</span> <span class="token function">getExp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> exp<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setExp</span><span class="token punctuation">(</span><span class="token keyword">long</span> exp<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>exp <span class="token operator">&#61;</span> exp<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getRole</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> role<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setRole</span><span class="token punctuation">(</span><span class="token class-name">String</span> role<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>role <span class="token operator">&#61;</span> role<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

上述代码中,创建了一个包含注册声明(sub、iat、exp)和私有声明(name、role)的载荷对象,然后将其转换为JSON字符串并进行Base64Url编码。

需要注意的是,Base64Url编码是可逆的,因此载荷中的信息可以被任何人解码读取。因此,不要在载荷中包含敏感信息,如密码、信用卡号等。如果需要传递敏感信息,应该对其进行加密处理。

2.4 签名(Signature)

签名是JWT的第三部分,它是由头部中指定的签名算法对编码后的头部、编码后的载荷以及一个密钥(对于HMAC算法)或私钥(对于RSA等非对称算法)进行计算得到的。

签名的作用是确保JWT在传输过程中没有被篡改,并且可以验证JWT的签发者是否是可信的。

签名的计算过程如下(以HS256算法为例):

signature = HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

其中,secret是服务器端保存的密钥,只有服务器知道这个密钥,用于生成和验证签名。

当接收方收到JWT时,会使用相同的算法和密钥(或公钥,对于非对称算法)重新计算签名,并与JWT中的签名进行比较。如果两者一致,则说明JWT没有被篡改,并且签发者是可信的;否则,JWT可能被篡改或来自不可信的来源,接收方应该拒绝该JWT。

以下是在Java中使用HS256算法生成签名的示例代码:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class JwtSignatureExample {
public static void main(String[] args) throws Exception {
// 编码后的头部和载荷
String encodedHeader = “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9”;
String encodedPayload = “eyJzdWIiOiIxMjM0NTY3ODkiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMiwicm9sZSI6ImFkbWluIn0”;

    <span class="token comment">// 密钥</span>
    <span class="token class-name">String</span> secret <span class="token operator">&#61;</span> <span class="token string">&#34;mySecretKey&#34;</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 计算签名</span>
    <span class="token class-name">String</span> signatureInput <span class="token operator">&#61;</span> encodedHeader <span class="token operator">&#43;</span> <span class="token string">&#34;.&#34;</span> <span class="token operator">&#43;</span> encodedPayload<span class="token punctuation">;</span>
    <span class="token class-name">Mac</span> mac <span class="token operator">&#61;</span> <span class="token class-name">Mac</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token string">&#34;HmacSHA256&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">SecretKeySpec</span> secretKeySpec <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">SecretKeySpec</span><span class="token punctuation">(</span>secret<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">&#34;HmacSHA256&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    mac<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>secretKeySpec<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> signatureBytes <span class="token operator">&#61;</span> mac<span class="token punctuation">.</span><span class="token function">doFinal</span><span class="token punctuation">(</span>signatureInput<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> encodedSignature <span class="token operator">&#61;</span> <span class="token class-name">Base64</span><span class="token punctuation">.</span><span class="token function">getUrlEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withoutPadding</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encodeToString</span><span class="token punctuation">(</span>signatureBytes<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Encoded Signature: &#34;</span> <span class="token operator">&#43;</span> encodedSignature<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 完整的JWT</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> encodedHeader <span class="token operator">&#43;</span> <span class="token string">&#34;.&#34;</span> <span class="token operator">&#43;</span> encodedPayload <span class="token operator">&#43;</span> <span class="token string">&#34;.&#34;</span> <span class="token operator">&#43;</span> encodedSignature<span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Complete JWT: &#34;</span> <span class="token operator">&#43;</span> jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

上述代码中,首先获取编码后的头部和载荷,然后将它们拼接起来,使用HMACSHA256算法和密钥计算签名,最后对签名进行Base64Url编码,得到JWT的第三部分。将这三部分拼接起来,就形成了完整的JWT。

对于RSA等非对称算法,签名的生成过程有所不同。使用私钥进行签名,接收方使用公钥进行验证。以下是使用RSA算法生成签名的示例代码(需要Java的密钥对生成相关类):

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class JwtRsaSignatureExample {
public static void main(String[] args) throws Exception {
// 生成RSA密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(“RSA”);
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();

    <span class="token comment">// 编码后的头部和载荷</span>
    <span class="token class-name">String</span> encodedHeader <span class="token operator">&#61;</span> <span class="token string">&#34;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> encodedPayload <span class="token operator">&#61;</span> <span class="token string">&#34;eyJzdWIiOiIxMjM0NTY3ODkiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMiwicm9sZSI6ImFkbWluIn0&#34;</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 使用私钥生成签名</span>
    <span class="token class-name">String</span> signatureInput <span class="token operator">&#61;</span> encodedHeader <span class="token operator">&#43;</span> <span class="token string">&#34;.&#34;</span> <span class="token operator">&#43;</span> encodedPayload<span class="token punctuation">;</span>
    <span class="token class-name">Signature</span> signature <span class="token operator">&#61;</span> <span class="token class-name">Signature</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token string">&#34;SHA256withRSA&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    signature<span class="token punctuation">.</span><span class="token function">initSign</span><span class="token punctuation">(</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
    signature<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>signatureInput<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> signatureBytes <span class="token operator">&#61;</span> signature<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> encodedSignature <span class="token operator">&#61;</span> <span class="token class-name">Base64</span><span class="token punctuation">.</span><span class="token function">getUrlEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withoutPadding</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encodeToString</span><span class="token punctuation">(</span>signatureBytes<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Encoded Signature (RSA): &#34;</span> <span class="token operator">&#43;</span> encodedSignature<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 完整的JWT</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> encodedHeader <span class="token operator">&#43;</span> <span class="token string">&#34;.&#34;</span> <span class="token operator">&#43;</span> encodedPayload <span class="token operator">&#43;</span> <span class="token string">&#34;.&#34;</span> <span class="token operator">&#43;</span> encodedSignature<span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Complete JWT (RSA): &#34;</span> <span class="token operator">&#43;</span> jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 验证签名&#xff08;模拟接收方&#xff09;</span>
    <span class="token class-name">Signature</span> verifier <span class="token operator">&#61;</span> <span class="token class-name">Signature</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token string">&#34;SHA256withRSA&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    verifier<span class="token punctuation">.</span><span class="token function">initVerify</span><span class="token punctuation">(</span>publicKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
    verifier<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>signatureInput<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">boolean</span> isValid <span class="token operator">&#61;</span> verifier<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span><span class="token class-name">Base64</span><span class="token punctuation">.</span><span class="token function">getUrlDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>encodedSignature<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Signature is valid: &#34;</span> <span class="token operator">&#43;</span> isValid<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

上述代码中,首先生成了RSA密钥对,然后使用私钥对头部和载荷的拼接字符串进行签名,生成JWT。接收方使用公钥对签名进行验证,确认JWT的有效性。

三、JWT的工作原理与认证流程

3.1 JWT的整体工作流程

JWT的工作流程主要包括以下几个步骤:

  1. 用户登录:用户通过用户名和密码等凭据向认证服务器发起登录请求。
  2. 生成JWT:认证服务器验证用户凭据的有效性,如果有效,则生成一个包含用户相关信息(如用户ID、角色等)的JWT,并使用服务器端的密钥对其进行签名。
  3. 返回JWT:认证服务器将生成的JWT返回给客户端。
  4. 客户端存储JWT:客户端(如浏览器、移动应用等)将JWT存储起来,通常可以存储在localStorage、sessionStorage或Cookie中。
  5. 客户端发送请求:当客户端需要访问受保护的资源时,将JWT包含在请求中(通常是在HTTP请求头的Authorization字段中,格式为“Bearer ”)。
  6. 服务器验证JWT:资源服务器收到请求后,从请求中提取JWT,验证其签名的有效性,检查JWT是否过期等。
  7. 处理请求:如果JWT验证通过,资源服务器根据JWT中的信息(如用户权限)处理客户端的请求,并返回相应的资源;如果验证失败,则拒绝请求。

整个流程如图3-1所示:

+----------------+      +----------------+      +----------------+
|    客户端      |      |   认证服务器    |      |   资源服务器    |
+----------------+      +----------------+      +----------------+
        |                        |                        |
        |  1. 登录请求(用户名、密码) |                        |
        |----------------------->|                        |
        |                        |                        |
        |                        |  2. 验证凭据,生成JWT  |
        |                        |<-----------------------|
        |                        |                        |
        |  3. 返回JWT            |                        |
        |<-----------------------|                        |
        |                        |                        |
        |  4. 存储JWT            |                        |
        |<-----------------------|                        |
        |                        |                        |
        |  5. 请求资源(携带JWT) |                        |
        |----------------------------------------------->|
        |                        |                        |
        |                        |  6. 验证JWT           |
        |                        |                        |
        |                        |  7. 处理请求,返回资源 |
        |<-----------------------------------------------|
        |                        |                        |

图3-1 JWT工作流程图

3.2 详细认证步骤解析
3.2.1 用户登录与JWT生成

用户登录是JWT认证流程的起点。用户在客户端输入用户名和密码等登录凭据后,客户端将这些凭据发送到认证服务器。

认证服务器接收到登录请求后,会进行以下操作:

  • 验证用户凭据的有效性:认证服务器会查询数据库或其他用户存储系统,检查用户名和密码是否匹配。如果凭据无效,认证服务器会返回错误信息,如“用户名或密码错误”。
  • 生成JWT的头部和载荷:如果用户凭据有效,认证服务器会生成JWT的头部和载荷。头部包含签名算法等信息,载荷包含用户的相关声明,如用户ID、角色、过期时间等。
  • 生成签名:认证服务器使用头部中指定的签名算法和服务器端的密钥(或私钥)对编码后的头部和载荷进行签名,生成JWT的签名部分。
  • 拼接JWT并返回:将编码后的头部、载荷和签名拼接起来,形成完整的JWT,并将其返回给客户端。

以下是在Java中实现认证服务器生成JWT的示例代码(使用jjwt库,jjwt是一个流行的Java JWT库):

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class AuthServer {
// 密钥,实际应用中应妥善保管,避免泄露
private static final String SECRET_KEY = “mySecretKey1234567890mySecretKey1234567890”;
// JWT过期时间,这里设置为1小时
private static final long EXPIRATION_TIME = 3600 * 1000;

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">generateToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> username<span class="token punctuation">,</span> <span class="token class-name">String</span> role<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 设置过期时间</span>
    <span class="token class-name">Date</span> expirationDate <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&#43;</span> EXPIRATION_TIME<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 设置载荷信息</span>
    <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> claims <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    claims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;sub&#34;</span><span class="token punctuation">,</span> username<span class="token punctuation">)</span><span class="token punctuation">;</span>
    claims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;role&#34;</span><span class="token punctuation">,</span> role<span class="token punctuation">)</span><span class="token punctuation">;</span>
    claims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;iat&#34;</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    claims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;exp&#34;</span><span class="token punctuation">,</span> expirationDate<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 生成JWT</span>
    <span class="token class-name">String</span> token <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setClaims</span><span class="token punctuation">(</span>claims<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">signWith</span><span class="token punctuation">(</span><span class="token class-name">SignatureAlgorithm</span><span class="token punctuation">.</span>HS256<span class="token punctuation">,</span> SECRET_KEY<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">compact</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">return</span> token<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 模拟用户登录&#xff0c;验证凭据成功后生成JWT</span>
    <span class="token class-name">String</span> username <span class="token operator">&#61;</span> <span class="token string">&#34;john_doe&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> role <span class="token operator">&#61;</span> <span class="token string">&#34;admin&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> <span class="token function">generateToken</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> role<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Generated JWT: &#34;</span> <span class="token operator">&#43;</span> jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

上述代码中,使用jjwt库的Jwts.builder()方法构建JWT,设置了载荷信息(包括用户ID、角色、签发时间和过期时间),使用HS256算法和密钥进行签名,最后生成并返回JWT。

3.2.2 客户端存储JWT

客户端收到认证服务器返回的JWT后,需要将其存储起来,以便在后续的请求中使用。常见的存储方式有以下几种:

  • localStorage:localStorage是HTML5提供的一种本地存储方式,它可以将数据持久化存储在客户端,即使关闭浏览器后数据也不会丢失。使用localStorage存储JWT的优点是操作简单,缺点是可能会受到XSS(跨站脚本攻击)的影响,因为JavaScript可以访问localStorage中的数据。
  • sessionStorage:sessionStorage也是HTML5提供的本地存储方式,但其存储的数据只在当前会话中有效,当关闭浏览器窗口后,数据会被删除。使用sessionStorage存储JWT的优点是安全性比localStorage稍高,因为数据不会持久化存储,缺点是在多个标签页或窗口之间不能共享数据。
  • Cookie:Cookie是一种传统的存储方式,它可以设置过期时间、路径、域名等属性。使用Cookie存储JWT时,可以设置HttpOnly属性,防止JavaScript访问Cookie,从而减少XSS攻击的风险;还可以设置Secure属性,只允许在HTTPS协议下传输Cookie,提高安全性。但Cookie存在跨域限制,并且可能会受到CSRF(跨站请求伪造)攻击。

在实际应用中,应根据具体的安全需求和应用场景选择合适的存储方式。例如,如果应用对安全性要求较高,可以选择使用Cookie并设置HttpOnly和Secure属性;如果需要在多个标签页之间共享JWT,可以选择使用localStorage,但需要加强对XSS攻击的防护。

以下是在前端JavaScript中使用localStorage存储JWT的示例代码:

// 假设从服务器获取到的JWT为token
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huX2RvZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTY2Mzg4NjQwMCwiZXhwIjoxNjYzODkwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

// 存储JWT到localStorage
localStorage.setItem(“jwtToken”, token);

// 从localStorage中获取JWT
const storedToken = localStorage.getItem(“jwtToken”);
console.log("Stored JWT: " + storedToken);

3.2.3 客户端发送请求与服务器验证JWT

当客户端需要访问受保护的资源时,需要将存储的JWT包含在请求中。最常见的方式是在HTTP请求头的Authorization字段中携带JWT,格式为“Bearer ”。

例如,在前端使用Axios发送请求的示例代码:

import axios from 'axios';

// 从localStorage中获取JWT
const token = localStorage.getItem(“jwtToken”);

// 设置请求头
axios.defaults.headers.common[‘Authorization’] = `Bearer ${token}`;

// 发送请求访问受保护的资源
axios.get(‘https://api.example.com/protected-resource’)
.then(response => {
console.log(‘Response:’, response.data);
})
.catch(error => {
console.error(‘Error:’, error.response.data);
});

资源服务器收到请求后,需要从请求头中提取JWT,并进行验证。验证过程主要包括以下几个步骤:

  1. 提取JWT:从请求头的Authorization字段中提取JWT,去除“Bearer ”前缀。
  2. 验证签名:使用与生成JWT时相同的签名算法和密钥(或公钥)验证JWT的签名,确保JWT没有被篡改。
  3. 检查过期时间:验证JWT的exp声明,确保JWT没有过期。
  4. 验证其他声明(可选):根据应用的需求,验证其他声明,如iss(签发者)、aud(受众)等。

如果JWT验证通过,资源服务器可以根据JWT中的信息(如用户角色)处理请求;如果验证失败,资源服务器返回401 Unauthorized或403 Forbidden等错误响应。

以下是在Java中实现资源服务器验证JWT的示例代码(使用jjwt库):

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;

public class ResourceServer {
private static final String SECRET_KEY = “mySecretKey1234567890mySecretKey1234567890”;

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">verifyToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 验证JWT的签名&#xff0c;并解析出 claims</span>
        <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">setSigningKey</span><span class="token punctuation">(</span>SECRET_KEY<span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">parseClaimsJws</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 检查过期时间&#xff08;jjwt会自动检查exp声明&#xff0c;如果过期会抛出ExpiredJwtException&#xff09;</span>
        <span class="token class-name">Claims</span> claims <span class="token operator">&#61;</span> claimsJws<span class="token punctuation">.</span><span class="token function">getBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 可以根据需要验证其他声明&#xff0c;如iss、aud等</span>
        <span class="token comment">// String issuer &#61; claims.getIssuer();</span>
        <span class="token comment">// if (!&#34;https://auth.example.com&#34;.equals(issuer)) {<!-- --></span>
        <span class="token comment">//     return false;</span>
        <span class="token comment">// }</span>
        
        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">SignatureException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 签名验证失败</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Invalid JWT signature&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token namespace">io<span class="token punctuation">.</span>jsonwebtoken<span class="token punctuation">.</span></span>ExpiredJwtException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// JWT已过期</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;JWT has expired&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 其他错误</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Invalid JWT&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Claims</span> <span class="token function">getClaimsFromToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
        <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">setSigningKey</span><span class="token punctuation">(</span>SECRET_KEY<span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">parseClaimsJws</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> claimsJws<span class="token punctuation">.</span><span class="token function">getBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> <span class="token string">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huX2RvZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTY2Mzg4NjQwMCwiZXhwIjoxNjYzODkwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&#34;</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 验证JWT</span>
    <span class="token keyword">boolean</span> isValid <span class="token operator">&#61;</span> <span class="token function">verifyToken</span><span class="token punctuation">(</span>jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;JWT is valid: &#34;</span> <span class="token operator">&#43;</span> isValid<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">if</span> <span class="token punctuation">(</span>isValid<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 获取JWT中的声明</span>
        <span class="token class-name">Claims</span> claims <span class="token operator">&#61;</span> <span class="token function">getClaimsFromToken</span><span class="token punctuation">(</span>jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Username: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;sub&#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Role: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;role&#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Issued At: &#34;</span> <span class="token operator">&#43;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">Long</span><span class="token punctuation">)</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;iat&#34;</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Expiration Time: &#34;</span> <span class="token operator">&#43;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">Long</span><span class="token punctuation">)</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;exp&#34;</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

上述代码中,verifyToken方法使用jjwt库验证JWT的签名和过期时间,如果验证通过返回true,否则返回false。getClaimsFromToken方法用于从JWT中解析出载荷信息(claims),以便资源服务器获取用户的相关信息。

3.3 JWT在不同场景下的应用
3.3.1 前后端分离架构

在前后端分离的架构中,前端和后端是两个独立的应用,它们通过API进行通信。JWT非常适合这种架构,因为它可以实现无状态的认证,减轻后端服务器的负担。

前端应用(如React、Vue等)负责用户界面的展示和与用户的交互,当用户登录时,前端将用户凭据发送到后端的认证服务器,认证服务器验证通过后返回JWT。前端将JWT存储起来,在后续的API请求中携带JWT,后端的资源服务器通过验证JWT来确认用户的身份和权限。

这种方式使得前端和后端可以独立部署和扩展,后端不需要关心前端的实现细节,只需要处理API请求和验证JWT即可。

3.3.2 分布式系统

在分布式系统中,多个服务之间需要进行通信和协作,用户的请求可能会经过多个服务。使用JWT可以在不同的服务之间安全地传递用户身份信息,而不需要每个服务都去查询用户数据库。

例如,用户的请求首先到达API网关,API网关验证JWT的有效性后,将JWT转发给后端的微服务。后端的微服务可以直接从JWT中获取用户信息,进行权限验证和业务处理,而不需要再次与认证服务器交互。

这种方式减少了服务之间的通信开销,提高了系统的性能和可扩展性。

3.3.3 移动应用认证

移动应用(如iOS、Android应用)与后端服务器的通信也可以使用JWT进行认证。移动应用在用户登录后获取JWT,并将其存储在本地(如SharedPreferences、Keychain等),在后续的请求中携带JWT。

与Web应用相比,移动应用通常不会受到Cookie相关的限制,使用JWT可以更灵活地进行认证。同时,JWT的紧凑性也适合在移动网络环境中传输。

四、Java中JWT的实现与实践

4.1 常用JWT库介绍

在Java中,有多个开源的JWT库可以用于生成和验证JWT,以下是几个常用的库:

4.1.1 JJWT(Java JWT)

JJWT是一个流行的、易于使用的JWT库,它提供了简洁的API来创建和验证JWT。JJWT支持多种签名算法,如HMAC、RSA、EC等,并且提供了丰富的功能,如设置过期时间、自定义声明等。

JJWT的优点是使用简单,文档丰富,社区活跃,是Java开发者实现JWT的首选库之一。

Maven依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
4.1.2 Nimbus JOSE + JWT

Nimbus JOSE + JWT是一个功能强大的JWT库,它支持JWT、JWS、JWE、JWA等多种规范,提供了全面的加密和签名功能。Nimbus JOSE + JWT适合对安全性和功能有较高要求的场景。

Maven依赖:

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>9.31</version>
</dependency>
4.1.3 Auth0 Java JWT

Auth0 Java JWT是Auth0公司开发的JWT库,它提供了简单的API来创建和验证JWT,支持多种签名算法。

Maven依赖:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>

在接下来的内容中,我们将以JJWT库为例,详细介绍如何在Java中实现JWT的生成、验证和解析等操作。

4.2 使用JJWT生成JWT

使用JJWT生成JWT的步骤如下:

  1. 创建JwtBuilder对象:通过Jwts.builder()方法创建JwtBuilder对象,用于构建JWT。
  2. 设置载荷信息:使用setClaims()方法设置自定义的载荷信息,也可以使用setSubject()setIssuer()等方法设置标准的注册声明。
  3. 设置签名算法和密钥:使用signWith()方法设置签名算法和密钥(或私钥)。
  4. 生成JWT:调用compact()方法生成JWT字符串。

以下是使用JJWT生成JWT的示例代码,包括设置不同的声明和使用不同的签名算法:

4.2.1 使用HMAC算法生成JWT
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtGeneratorWithHMAC {
// 密钥,实际应用中应使用足够长且随机的密钥
private static final String SECRET_KEY = “thisIsASecretKeyWithEnoughLengthToMeetTheRequirementsOfHmacSha256Algorithm”;
// 过期时间,1小时
private static final long EXPIRATION_TIME = 3600 * 1000;

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">generateToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> username<span class="token punctuation">,</span> <span class="token class-name">String</span> role<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 当前时间</span>
    <span class="token class-name">Date</span> now <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 过期时间</span>
    <span class="token class-name">Date</span> expirationDate <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>now<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&#43;</span> EXPIRATION_TIME<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 自定义载荷</span>
    <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> customClaims <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    customClaims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;role&#34;</span><span class="token punctuation">,</span> role<span class="token punctuation">)</span><span class="token punctuation">;</span>
    customClaims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;department&#34;</span><span class="token punctuation">,</span> <span class="token string">&#34;IT&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 生成JWT</span>
    <span class="token class-name">String</span> token <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token comment">// 设置标准声明</span>
            <span class="token punctuation">.</span><span class="token function">setSubject</span><span class="token punctuation">(</span>username<span class="token punctuation">)</span> <span class="token comment">// 主题&#xff08;用户名&#xff09;</span>
            <span class="token punctuation">.</span><span class="token function">setIssuer</span><span class="token punctuation">(</span><span class="token string">&#34;https://auth.example.com&#34;</span><span class="token punctuation">)</span> <span class="token comment">// 签发者</span>
            <span class="token punctuation">.</span><span class="token function">setAudience</span><span class="token punctuation">(</span><span class="token string">&#34;https://api.example.com&#34;</span><span class="token punctuation">)</span> <span class="token comment">// 受众</span>
            <span class="token punctuation">.</span><span class="token function">setIssuedAt</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span> <span class="token comment">// 签发时间</span>
            <span class="token punctuation">.</span><span class="token function">setExpiration</span><span class="token punctuation">(</span>expirationDate<span class="token punctuation">)</span> <span class="token comment">// 过期时间</span>
            <span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token string">&#34;jwt-&#34;</span> <span class="token operator">&#43;</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// JWT ID</span>
            <span class="token comment">// 设置自定义声明</span>
            <span class="token punctuation">.</span><span class="token function">addClaims</span><span class="token punctuation">(</span>customClaims<span class="token punctuation">)</span>
            <span class="token comment">// 设置签名算法和密钥</span>
            <span class="token punctuation">.</span><span class="token function">signWith</span><span class="token punctuation">(</span><span class="token class-name">SignatureAlgorithm</span><span class="token punctuation">.</span>HS256<span class="token punctuation">,</span> SECRET_KEY<span class="token punctuation">)</span>
            <span class="token comment">// 生成JWT</span>
            <span class="token punctuation">.</span><span class="token function">compact</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">return</span> token<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token class-name">String</span> username <span class="token operator">&#61;</span> <span class="token string">&#34;john_doe&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> role <span class="token operator">&#61;</span> <span class="token string">&#34;admin&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> <span class="token function">generateToken</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> role<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Generated JWT with HMAC: &#34;</span> <span class="token operator">&#43;</span> jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

上述代码中,使用HS256算法(HMAC SHA256)生成JWT,设置了标准的注册声明(subject、issuer、audience、issuedAt、expiration、id)和自定义声明(role、department)。

4.2.2 使用RSA算法生成JWT

使用RSA算法生成JWT需要先生成RSA密钥对(公钥和私钥),使用私钥进行签名,公钥用于验证签名。

以下是生成RSA密钥对并使用私钥生成JWT的示例代码:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtGeneratorWithRSA {
// 生成RSA密钥对
private static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(“RSA”);
keyPairGenerator.initialize(2048); // 密钥长度为2048位
return keyPairGenerator.generateKeyPair();
}

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">generateTokenWithRSA</span><span class="token punctuation">(</span><span class="token class-name">String</span> username<span class="token punctuation">,</span> <span class="token class-name">String</span> role<span class="token punctuation">,</span> <span class="token class-name">KeyPair</span> keyPair<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 当前时间</span>
    <span class="token class-name">Date</span> now <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 过期时间&#xff0c;1小时</span>
    <span class="token keyword">long</span> EXPIRATION_TIME <span class="token operator">&#61;</span> <span class="token number">3600</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">;</span>
    <span class="token class-name">Date</span> expirationDate <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>now<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&#43;</span> EXPIRATION_TIME<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 自定义载荷</span>
    <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> customClaims <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    customClaims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;role&#34;</span><span class="token punctuation">,</span> role<span class="token punctuation">)</span><span class="token punctuation">;</span>
    customClaims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;department&#34;</span><span class="token punctuation">,</span> <span class="token string">&#34;IT&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 使用私钥生成JWT</span>
    <span class="token class-name">String</span> token <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setSubject</span><span class="token punctuation">(</span>username<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setIssuer</span><span class="token punctuation">(</span><span class="token string">&#34;https://auth.example.com&#34;</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setIssuedAt</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setExpiration</span><span class="token punctuation">(</span>expirationDate<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">addClaims</span><span class="token punctuation">(</span>customClaims<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">signWith</span><span class="token punctuation">(</span>keyPair<span class="token punctuation">.</span><span class="token function">getPrivate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">SignatureAlgorithm</span><span class="token punctuation">.</span>RS256<span class="token punctuation">)</span> <span class="token comment">// 使用RSA私钥签名</span>
            <span class="token punctuation">.</span><span class="token function">compact</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">return</span> token<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">NoSuchAlgorithmException</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 生成RSA密钥对</span>
    <span class="token class-name">KeyPair</span> keyPair <span class="token operator">&#61;</span> <span class="token function">generateRsaKeyPair</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token class-name">String</span> username <span class="token operator">&#61;</span> <span class="token string">&#34;jane_smith&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> role <span class="token operator">&#61;</span> <span class="token string">&#34;user&#34;</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> <span class="token function">generateTokenWithRSA</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> role<span class="token punctuation">,</span> keyPair<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Generated JWT with RSA: &#34;</span> <span class="token operator">&#43;</span> jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

上述代码中,首先生成了RSA密钥对,然后使用私钥和RS256算法(RSA SHA256)生成JWT。

4.3 使用JJWT验证和解析JWT

使用JJWT验证和解析JWT的步骤如下:

  1. 创建JwtParser对象:通过Jwts.parserBuilder()方法创建JwtParserBuilder对象,然后设置签名密钥(或公钥),并构建JwtParser对象。
  2. 解析JWT:使用parseClaimsJws()方法解析JWT字符串,得到Jws<Claims>对象,其中包含了解析后的头部和载荷信息。
  3. 验证和获取信息:从Jws<Claims>对象中获取载荷信息(Claims),可以验证JWT的过期时间、签发者等信息,也可以获取自定义声明。

以下是使用JJWT验证和解析JWT的示例代码:

4.3.1 验证和解析使用HMAC算法生成的JWT
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;

public class JwtVerifierWithHMAC {
private static final String SECRET_KEY = “thisIsASecretKeyWithEnoughLengthToMeetTheRequirementsOfHmacSha256Algorithm”;

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> <span class="token function">verifyAndParseToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 创建密钥&#xff08;对于HMAC算法&#xff0c;需要将密钥字符串转换为Key对象&#xff09;</span>
        <span class="token class-name">Key</span> key <span class="token operator">&#61;</span> <span class="token class-name">Keys</span><span class="token punctuation">.</span><span class="token function">hmacShaKeyFor</span><span class="token punctuation">(</span>SECRET_KEY<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 解析和验证JWT</span>
        <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">parserBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">setSigningKey</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span> <span class="token comment">// 设置签名密钥</span>
                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">parseClaimsJws</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 解析JWT</span>
        
        <span class="token keyword">return</span> claimsJws<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 验证失败&#xff0c;抛出异常或返回null</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;JWT verification failed: &#34;</span> <span class="token operator">&#43;</span> e<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 假设这是从客户端接收到的JWT</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> <span class="token string">&#34;eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huX2RvZSIsImlzcyI6Imh0dHBzOi8vYXV0aC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNjYzODg4MDAwLCJleHAiOjE2NjM4OTE2MDAsImp0aSI6Imp3dC0xNjYzODg4MDAwMDAwIiwicm9sZSI6ImFkbWluIiwiZGVwYXJ0bWVudCI6IklUIn0.2lZJZJZJZJZJZJZJZJZJZJZJZJZJZJZJZJZJZJZ&#34;</span><span class="token punctuation">;</span>
    
    <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token function">verifyAndParseToken</span><span class="token punctuation">(</span>jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>claimsJws <span class="token operator">!&#61;</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token class-name">Claims</span> claims <span class="token operator">&#61;</span> claimsJws<span class="token punctuation">.</span><span class="token function">getBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 获取标准声明</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Subject: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">getSubject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Issuer: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">getIssuer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Audience: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">getAudience</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Issued At: &#34;</span> <span class="token operator">&#43;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>claims<span class="token punctuation">.</span><span class="token function">getIssuedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Expiration Time: &#34;</span> <span class="token operator">&#43;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>claims<span class="token punctuation">.</span><span class="token function">getExpiration</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;JWT ID: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 获取自定义声明</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Role: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;role&#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Department: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;department&#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

上述代码中,使用与生成JWT时相同的密钥创建Key对象,然后使用JwtParser解析和验证JWT。如果验证通过,将返回包含载荷信息的Jws<Claims>对象,从中可以获取各种声明信息。

4.3.2 验证和解析使用RSA算法生成的JWT
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

public class JwtVerifierWithRSA {
// 生成RSA密钥对(实际应用中,公钥应从可信来源获取)
private static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(“RSA”);
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> <span class="token function">verifyAndParseTokenWithRSA</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">,</span> <span class="token class-name">KeyPair</span> keyPair<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 使用公钥验证签名</span>
        <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">parserBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">setSigningKey</span><span class="token punctuation">(</span>keyPair<span class="token punctuation">.</span><span class="token function">getPublic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 设置RSA公钥</span>
                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">parseClaimsJws</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token keyword">return</span> claimsJws<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;JWT verification with RSA failed: &#34;</span> <span class="token operator">&#43;</span> e<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">NoSuchAlgorithmException</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 生成RSA密钥对&#xff08;实际应用中&#xff0c;公钥应分发给资源服务器&#xff09;</span>
    <span class="token class-name">KeyPair</span> keyPair <span class="token operator">&#61;</span> <span class="token function">generateRsaKeyPair</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 生成JWT&#xff08;模拟认证服务器生成&#xff09;</span>
    <span class="token class-name">String</span> jwt <span class="token operator">&#61;</span> <span class="token class-name">JwtGeneratorWithRSA</span><span class="token punctuation">.</span><span class="token function">generateTokenWithRSA</span><span class="token punctuation">(</span><span class="token string">&#34;jane_smith&#34;</span><span class="token punctuation">,</span> <span class="token string">&#34;user&#34;</span><span class="token punctuation">,</span> keyPair<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Generated JWT: &#34;</span> <span class="token operator">&#43;</span> jwt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 验证和解析JWT&#xff08;模拟资源服务器操作&#xff09;</span>
    <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token function">verifyAndParseTokenWithRSA</span><span class="token punctuation">(</span>jwt<span class="token punctuation">,</span> keyPair<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>claimsJws <span class="token operator">!&#61;</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token class-name">Claims</span> claims <span class="token operator">&#61;</span> claimsJws<span class="token punctuation">.</span><span class="token function">getBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Subject: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">getSubject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Issuer: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">getIssuer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Role: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;role&#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;Department: &#34;</span> <span class="token operator">&#43;</span> claims<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&#34;department&#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

上述代码中,资源服务器使用RSA公钥来验证JWT的签名,这是因为RSA是一种非对称加密算法,使用私钥签名,公钥验证,这样可以避免密钥的共享,提高安全性。

4.4 JWT在Spring Boot中的集成

Spring Boot是目前流行的Java开发框架,将JWT与Spring Boot集成可以实现基于JWT的认证和授权功能。以下是集成的步骤:

4.4.1 添加依赖

在Spring Boot项目的pom.xml文件中添加JJWT和Spring Security的依赖:

<dependencies>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
<span class="token comment">&lt;!-- JJWT --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">&gt;</span></span>io.jsonwebtoken<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">&gt;</span></span>jjwt-api<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">&gt;</span></span>0.11.5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">&gt;</span></span>io.jsonwebtoken<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">&gt;</span></span>jjwt-impl<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">&gt;</span></span>0.11.5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">&gt;</span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">&gt;</span></span>io.jsonwebtoken<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">&gt;</span></span>jjwt-jackson<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">&gt;</span></span>0.11.5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">&gt;</span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">&gt;</span></span>

<span class="token comment">&lt;!-- Spring Web --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">&gt;</span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">&gt;</span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">&gt;</span></span>

</dependencies>

4.4.2 配置JWT工具类

创建一个JWT工具类,用于生成、验证和解析JWT:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtTokenUtil {
@Value(“${jwt.secret}”)
private String secretKey;

<span class="token annotation punctuation">&#64;Value</span><span class="token punctuation">(</span><span class="token string">&#34;${jwt.expiration}&#34;</span><span class="token punctuation">)</span>
<span class="token keyword">private</span> <span class="token keyword">long</span> expirationTime<span class="token punctuation">;</span> <span class="token comment">// 毫秒</span>

<span class="token comment">// 生成密钥</span>
<span class="token keyword">private</span> <span class="token class-name">Key</span> <span class="token function">getSigningKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> keyBytes <span class="token operator">&#61;</span> secretKey<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token class-name">Keys</span><span class="token punctuation">.</span><span class="token function">hmacShaKeyFor</span><span class="token punctuation">(</span>keyBytes<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 从JWT中获取用户名</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">extractUsername</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token function">extractClaim</span><span class="token punctuation">(</span>token<span class="token punctuation">,</span> <span class="token class-name">Claims</span><span class="token operator">::</span><span class="token function">getSubject</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 从JWT中获取过期时间</span>
<span class="token keyword">public</span> <span class="token class-name">Date</span> <span class="token function">extractExpiration</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token function">extractClaim</span><span class="token punctuation">(</span>token<span class="token punctuation">,</span> <span class="token class-name">Claims</span><span class="token operator">::</span><span class="token function">getExpiration</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 从JWT中获取指定的声明</span>
<span class="token keyword">public</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">T</span><span class="token punctuation">&gt;</span></span> <span class="token class-name">T</span> <span class="token function">extractClaim</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">,</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token punctuation">&gt;</span></span> claimsResolver<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">final</span> <span class="token class-name">Claims</span> claims <span class="token operator">&#61;</span> <span class="token function">extractAllClaims</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> claimsResolver<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>claims<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 从JWT中获取所有声明</span>
<span class="token keyword">private</span> <span class="token class-name">Claims</span> <span class="token function">extractAllClaims</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">parserBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setSigningKey</span><span class="token punctuation">(</span><span class="token function">getSigningKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">parseClaimsJws</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">getBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 检查JWT是否过期</span>
<span class="token keyword">private</span> <span class="token class-name">Boolean</span> <span class="token function">isTokenExpired</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token function">extractExpiration</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">before</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 为用户生成JWT</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">generateToken</span><span class="token punctuation">(</span><span class="token class-name">UserDetails</span> userDetails<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> claims <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 可以添加自定义声明&#xff0c;如用户角色等</span>
    claims<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">&#34;roles&#34;</span><span class="token punctuation">,</span> userDetails<span class="token punctuation">.</span><span class="token function">getAuthorities</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token function">createToken</span><span class="token punctuation">(</span>claims<span class="token punctuation">,</span> userDetails<span class="token punctuation">.</span><span class="token function">getUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 创建JWT</span>
<span class="token keyword">private</span> <span class="token class-name">String</span> <span class="token function">createToken</span><span class="token punctuation">(</span><span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> claims<span class="token punctuation">,</span> <span class="token class-name">String</span> subject<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token class-name">Date</span> now <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">Date</span> expirationDate <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>now<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&#43;</span> expirationTime<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">return</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setClaims</span><span class="token punctuation">(</span>claims<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setSubject</span><span class="token punctuation">(</span>subject<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setIssuedAt</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setExpiration</span><span class="token punctuation">(</span>expirationDate<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">signWith</span><span class="token punctuation">(</span><span class="token function">getSigningKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">SignatureAlgorithm</span><span class="token punctuation">.</span>HS256<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">compact</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 验证JWT是否有效&#xff08;用户名匹配且未过期&#xff09;</span>
<span class="token keyword">public</span> <span class="token class-name">Boolean</span> <span class="token function">validateToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">,</span> <span class="token class-name">UserDetails</span> userDetails<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">final</span> <span class="token class-name">String</span> username <span class="token operator">&#61;</span> <span class="token function">extractUsername</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span>username<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>userDetails<span class="token punctuation">.</span><span class="token function">getUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token function">isTokenExpired</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

4.4.3 配置Spring Security

创建Spring Security配置类,配置认证和授权规则,以及JWT过滤器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

<span class="token annotation punctuation">&#64;Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">UserDetailsService</span> jwtUserDetailsService<span class="token punctuation">;</span>

<span class="token annotation punctuation">&#64;Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">JwtRequestFilter</span> jwtRequestFilter<span class="token punctuation">;</span>

<span class="token annotation punctuation">&#64;Bean</span>
<span class="token keyword">public</span> <span class="token class-name">AuthenticationManager</span> <span class="token function">authenticationManager</span><span class="token punctuation">(</span><span class="token class-name">AuthenticationConfiguration</span> authConfig<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> authConfig<span class="token punctuation">.</span><span class="token function">getAuthenticationManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token annotation punctuation">&#64;Bean</span>
<span class="token keyword">public</span> <span class="token class-name">PasswordEncoder</span> <span class="token function">passwordEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">BCryptPasswordEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token annotation punctuation">&#64;Bean</span>
<span class="token keyword">public</span> <span class="token class-name">SecurityFilterChain</span> <span class="token function">filterChain</span><span class="token punctuation">(</span><span class="token class-name">HttpSecurity</span> http<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">{<!-- --></span>
    http<span class="token punctuation">.</span><span class="token function">cors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">and</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">csrf</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">disable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token comment">// 未认证请求的处理</span>
            <span class="token punctuation">.</span><span class="token function">exceptionHandling</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">authenticationEntryPoint</span><span class="token punctuation">(</span>jwtAuthenticationEntryPoint<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">and</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token comment">// 无状态会话&#xff0c;不创建Session</span>
            <span class="token punctuation">.</span><span class="token function">sessionManagement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sessionCreationPolicy</span><span class="token punctuation">(</span><span class="token class-name">SessionCreationPolicy</span><span class="token punctuation">.</span>STATELESS<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">and</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token comment">// 配置URL访问权限</span>
            <span class="token punctuation">.</span><span class="token function">authorizeRequests</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">antMatchers</span><span class="token punctuation">(</span><span class="token string">&#34;/authenticate&#34;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">permitAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 登录接口允许匿名访问</span>
            <span class="token punctuation">.</span><span class="token function">anyRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">authenticated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 其他接口需要认证</span>
    
    <span class="token comment">// 添加JWT过滤器&#xff0c;在UsernamePasswordAuthenticationFilter之前执行</span>
    http<span class="token punctuation">.</span><span class="token function">addFilterBefore</span><span class="token punctuation">(</span>jwtRequestFilter<span class="token punctuation">,</span> <span class="token class-name">UsernamePasswordAuthenticationFilter</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">return</span> http<span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

4.4.4 实现JWT认证过滤器

创建JWT认证过滤器,用于从请求中提取JWT,验证JWT的有效性,并将认证信息存入Spring Security的上下文:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService jwtUserDetailsService;

<span class="token annotation punctuation">&#64;Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">JwtTokenUtil</span> jwtTokenUtil<span class="token punctuation">;</span>

<span class="token annotation punctuation">&#64;Override</span>
<span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">doFilterInternal</span><span class="token punctuation">(</span><span class="token class-name">HttpServletRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">HttpServletResponse</span> response<span class="token punctuation">,</span> <span class="token class-name">FilterChain</span> chain<span class="token punctuation">)</span>
        <span class="token keyword">throws</span> <span class="token class-name">ServletException</span><span class="token punctuation">,</span> <span class="token class-name">IOException</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">final</span> <span class="token class-name">String</span> requestTokenHeader <span class="token operator">&#61;</span> request<span class="token punctuation">.</span><span class="token function">getHeader</span><span class="token punctuation">(</span><span class="token string">&#34;Authorization&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token class-name">String</span> username <span class="token operator">&#61;</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> jwtToken <span class="token operator">&#61;</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    
    <span class="token comment">// JWT通常在Authorization头中&#xff0c;格式为&#34;Bearer &lt;token&gt;&#34;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>requestTokenHeader <span class="token operator">!&#61;</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> requestTokenHeader<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">&#34;Bearer &#34;</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        jwtToken <span class="token operator">&#61;</span> requestTokenHeader<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
            username <span class="token operator">&#61;</span> jwtTokenUtil<span class="token punctuation">.</span><span class="token function">extractUsername</span><span class="token punctuation">(</span>jwtToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">&#34;Unable to get JWT Token&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span>
        logger<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">&#34;JWT Token does not begin with Bearer String&#34;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token comment">// 验证token并设置认证信息</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>username <span class="token operator">!&#61;</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token class-name">SecurityContextHolder</span><span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAuthentication</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&#61;&#61;</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token class-name">UserDetails</span> userDetails <span class="token operator">&#61;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>jwtUserDetailsService<span class="token punctuation">.</span><span class="token function">loadUserByUsername</span><span class="token punctuation">(</span>username<span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 如果token有效&#xff0c;配置Spring Security</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>jwtTokenUtil<span class="token punctuation">.</span><span class="token function">validateToken</span><span class="token punctuation">(</span>jwtToken<span class="token punctuation">,</span> userDetails<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token class-name">UsernamePasswordAuthenticationToken</span> usernamePasswordAuthenticationToken <span class="token operator">&#61;</span> <span class="token keyword">new</span> <span class="token class-name">UsernamePasswordAuthenticationToken</span><span class="token punctuation">(</span>
                    userDetails<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> userDetails<span class="token punctuation">.</span><span class="token function">getAuthorities</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            usernamePasswordAuthenticationToken
                    <span class="token punctuation">.</span><span class="token function">setDetails</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">WebAuthenticationDetailsSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">buildDetails</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            
            <span class="token comment">// 设置认证信息到SecurityContext</span>
            <span class="token class-name">SecurityContextHolder</span><span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setAuthentication</span><span class="token punctuation">(</span>usernamePasswordAuthenticationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    chain<span class="token punctuation">.</span><span class="token function">doFilter</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

4.4.5 实现登录接口

创建登录接口,用于接收用户的登录请求,验证凭据后生成JWT并返回:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JwtAuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;

<span class="token annotation punctuation">&#64;Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">JwtTokenUtil</span> jwtTokenUtil<span class="token punctuation">;</span>

<span class="token annotation punctuation">&#64;Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">UserDetailsService</span> userDetailsService<span class="token punctuation">;</span>

<span class="token annotation punctuation">&#64;PostMapping</span><span class="token punctuation">(</span><span class="token string">&#34;/authenticate&#34;</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">ResponseEntity</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span></span> <span class="token function">createAuthenticationToken</span><span class="token punctuation">(</span><span class="token annotation punctuation">&#64;RequestBody</span> <span class="token class-name">JwtRequest</span> authenticationRequest<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
        authenticationManager<span class="token punctuation">.</span><span class="token function">authenticate</span><span class="token punctuation">(</span>
                <span class="token keyword">new</span> <span class="token class-name">UsernamePasswordAuthenticationToken</span><span class="token punctuation">(</span>authenticationRequest<span class="token punctuation">.</span><span class="token function">getUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> authenticationRequest<span class="token punctuation">.</span><span class="token function">getPassword</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">BadCredentialsException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token string">&#34;Invalid username or password&#34;</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token keyword">final</span> <span class="token class-name">UserDetails</span> userDetails <span class="token operator">&#61;</span> userDetailsService
            <span class="token punctuation">.</span><span class="token function">loadUserByUsername</span><span class="token punctuation">(</span>authenticationRequest<span class="token punctuation">.</span><span class="token function">getUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">final</span> <span class="token class-name">String</span> token <span class="token operator">&#61;</span> jwtTokenUtil<span class="token punctuation">.</span><span class="token function">generateToken</span><span class="token punctuation">(</span>userDetails<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">return</span> <span class="token class-name">ResponseEntity</span><span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">JwtResponse</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

// JwtRequest和JwtResponse类
class JwtRequest {
private String username;
private String password;

<span class="token comment">// getter和setter方法</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> username<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setUsername</span><span class="token punctuation">(</span><span class="token class-name">String</span> username<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>username <span class="token operator">&#61;</span> username<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getPassword</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> password<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setPassword</span><span class="token punctuation">(</span><span class="token class-name">String</span> password<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>password <span class="token operator">&#61;</span> password<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

class JwtResponse {
private final String jwtToken;

<span class="token keyword">public</span> <span class="token class-name">JwtResponse</span><span class="token punctuation">(</span><span class="token class-name">String</span> jwtToken<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>jwtToken <span class="token operator">&#61;</span> jwtToken<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> jwtToken<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

4.4.6 实现用户详情服务

实现UserDetailsService接口,用于加载用户信息:

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

@Service
public class JwtUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 实际应用中应从数据库中查询用户信息
if (“john_doe”.equals(username)) {
return new User(“john_doe”, “$2a101010slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6”,
new ArrayList<>());
} else {
throw new UsernameNotFoundException("User not found with username: " + username);
}
}
}

上述代码中,用户密码使用BCrypt加密,实际应用中应从数据库中查询用户信息并返回。

4.4.7 配置应用属性

application.properties文件中配置JWT的密钥和过期时间:

jwt.secret=thisIsASecretKeyWithEnoughLengthForHmacSha256AlgorithmInSpringBootApplication
jwt.expiration=3600000

通过以上步骤,就完成了JWT在Spring Boot中的集成,实现了基于JWT的认证功能。当用户登录时,系统会生成JWT并返回给客户端,客户端在后续的请求中携带JWT,系统通过验证JWT来确认用户的身份和权限。

五、JWT的安全性分析与最佳实践

5.1 JWT的安全风险

尽管JWT具有诸多优势,但在使用过程中也存在一些安全风险,需要引起开发者的重视:

5.1.1 令牌泄露风险

JWT是用户身份的凭证,如果JWT被泄露,攻击者可以使用该令牌冒充用户访问受保护的资源。JWT的泄露可能发生在以下场景:

  • 网络传输过程中:如果JWT在未加密的网络(如HTTP)中传输,可能会被窃听,导致令牌泄露。
  • 客户端存储不安全:如果客户端将JWT存储在不安全的位置(如localStorage),可能会受到XSS攻击,导致令牌被窃取。
  • 日志泄露:如果服务器在日志中记录了JWT,而日志被泄露,也会导致令牌泄露。
5.1.2 无法撤销的问题

JWT一旦生成,在过期之前始终有效,服务器无法主动撤销已生成的JWT。这可能会带来以下问题:

  • 用户注销登录后:用户注销登录后,客户端可能仍然持有有效的JWT,攻击者如果获取到该令牌,仍然可以访问资源。
  • 用户权限变更后:如果用户的权限发生了变更(如从管理员降为普通用户),但之前生成的JWT仍然包含旧的权限信息,使用该令牌仍然可以访问原本有权限的资源。
  • 令牌被盗后:如果JWT被盗,在令牌过期之前,攻击者可以一直使用该令牌,服务器无法立即阻止。
5.1.3 载荷信息泄露

JWT的载荷部分只是经过Base64Url编码,而不是加密,因此任何人都可以解码获取载荷中的信息。如果在载荷中包含了敏感信息(如密码、信用卡号等),这些信息可能会被泄露。

5.1.4 签名算法安全问题

如果使用了不安全的签名算法(如None算法,即不进行签名),或者密钥管理不当(如密钥过短、密钥泄露等),可能会导致JWT被篡改或伪造。

例如,攻击者如果获取到了使用弱密钥的JWT,可以通过暴力破解等方式获取密钥,然后伪造JWT。

5.2 安全防范措施

针对上述安全风险,可以采取以下防范措施:

5.2.1 防止令牌泄露
  • 使用HTTPS协议:在传输JWT时,应使用HTTPS协议,对传输的数据进行加密,防止被窃听。
  • 安全存储JWT
    • 对于Web应用,尽量使用Cookie存储JWT,并设置HttpOnly和Secure属性。HttpOnly属性可以防止JavaScript访问Cookie,减少XSS攻击的风险;Secure属性可以确保Cookie只在HTTPS协议下传输。
    • 对于移动应用,应将JWT存储在安全的位置,如Android的KeyStore或iOS的Keychain。
  • 避免在日志中记录JWT:服务器日志中不应包含完整的JWT,以防止日志泄露导致令牌泄露。
  • 设置合理的过期时间:将JWT的过期时间设置得短一些(如15分钟到1小时),即使令牌泄露,攻击者可以利用的时间也有限。
5.2.2 解决无法撤销的问题
  • 使用刷新令牌(Refresh Token):刷新令牌是一种长期有效的令牌,用于在JWT过期后获取新的JWT。当用户注销登录时,可以将刷新令牌加入黑名单,使其失效,从而无法获取新的JWT。JWT的过期时间可以设置得较短,而刷新令牌的过期时间可以设置得较长。
  • 维护JWT黑名单:对于需要立即撤销的JWT(如用户注销、令牌被盗等),可以将其加入黑名单。服务器在验证JWT时,先检查该JWT是否在黑名单中,如果在,则拒绝该令牌。黑名单可以使用Redis等缓存系统存储,设置与JWT过期时间相同的过期时间,以减少存储压力。

以下是使用Redis维护JWT黑名单的示例代码:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class JwtBlacklist {
private final RedisTemplate<String, Object> redisTemplate;

<span class="token keyword">public</span> <span class="token class-name">JwtBlacklist</span><span class="token punctuation">(</span><span class="token class-name">RedisTemplate</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> redisTemplate<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate <span class="token operator">&#61;</span> redisTemplate<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 将JWT加入黑名单</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addToBlacklist</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">,</span> <span class="token keyword">long</span> expirationTime<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// 过期时间设置为JWT的剩余有效时间</span>
    redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">&#34;jwt:blacklist:&#34;</span> <span class="token operator">&#43;</span> token<span class="token punctuation">,</span> <span class="token string">&#34;invalid&#34;</span><span class="token punctuation">,</span> expirationTime<span class="token punctuation">,</span> <span class="token class-name">TimeUnit</span><span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 检查JWT是否在黑名单中</span>
<span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isInBlacklist</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> redisTemplate<span class="token punctuation">.</span><span class="token function">hasKey</span><span class="token punctuation">(</span><span class="token string">&#34;jwt:blacklist:&#34;</span> <span class="token operator">&#43;</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

在验证JWT时,先检查该JWT是否在黑名单中:

public Jws<Claims> verifyAndParseToken(String token) {
    // 检查是否在黑名单中
    if (jwtBlacklist.isInBlacklist(token)) {
        System.err.println("JWT is in blacklist");
        return null;
    }
<span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
    <span class="token class-name">Key</span> key <span class="token operator">&#61;</span> <span class="token class-name">Keys</span><span class="token punctuation">.</span><span class="token function">hmacShaKeyFor</span><span class="token punctuation">(</span>SECRET_KEY<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">Jws</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Claims</span><span class="token punctuation">&gt;</span></span> claimsJws <span class="token operator">&#61;</span> <span class="token class-name">Jwts</span><span class="token punctuation">.</span><span class="token function">parserBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">setSigningKey</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">parseClaimsJws</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> claimsJws<span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&#34;JWT verification failed: &#34;</span> <span class="token operator">&#43;</span> e<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

5.2.3 保护载荷信息
  • 不存储敏感信息:不在JWT的载荷中包含任何敏感信息,如密码、银行卡号等。
  • 对敏感信息进行加密:如果确实需要在载荷中包含一些敏感信息,应对这些信息进行加密处理,再放入载荷中。
5.2.4 确保签名算法安全
  • 使用安全的签名算法:选择安全的签名算法,如HS256、RS256等,避免使用不安全的算法(如None)。
  • 妥善管理密钥
    • 对于HMAC算法,使用足够长且随机的密钥,密钥长度应至少与签名算法的要求一致(如HS256要求密钥长度至少为256位)。
    • 对于RSA等非对称算法,使用足够长的密钥(如2048位或更长),并妥善保管私钥,公钥可以公开分发。
    • 定期更换密钥,以降低密钥泄露的风险。
5.3 JWT的最佳实践
5.3.1 合理设置过期时间

JWT的过期时间(exp)应根据应用的安全需求和用户体验进行设置。过期时间过短,会导致用户需要频繁登录,影响用户体验;过期时间过长,会增加令牌泄露的风险。

一般来说,对于Web应用,JWT的过期时间可以设置为15分钟到1小时,对于移动应用,可以适当延长,但不应过长。同时,可以使用刷新令牌机制,在JWT过期后自动获取新的JWT,减少用户登录的频率。

5.3.2 避免在JWT中包含过多信息

JWT的体积过大会增加网络传输的负担,影响系统性能。因此,应只在载荷中包含必要的信息,如用户ID、角色等,避免包含过多的冗余信息。

5.3.3 使用HTTPS协议

始终使用HTTPS协议传输JWT,以确保JWT在传输过程中的安全性,防止被窃听和篡改。

5.3.4 实现刷新令牌机制

刷新令牌机制可以在不要求用户重新登录的情况下,获取新的JWT,既提高了用户体验,又可以在一定程度上解决JWT无法撤销的问题。

刷新令牌的过期时间可以设置得较长(如7天、30天等),但应存储在安全的位置,并可以被撤销。当用户注销登录时,应同时将刷新令牌加入黑名单,使其无法再获取新的JWT。

5.3.5 定期轮换密钥

定期更换用于签名JWT的密钥,可以降低密钥泄露带来的风险。在更换密钥时,应确保旧的JWT在过期前仍然可以被验证,避免影响用户体验。可以采用以下策略:

  • 同时支持新旧两个密钥一段时间,在这段时间内,既可以使用旧密钥验证旧的JWT,也可以使用新密钥验证新的JWT。
  • 当旧的JWT全部过期后,停止使用旧密钥。
5.3.6 对JWT进行验证和校验

在接收JWT后,必须对其进行严格的验证,包括签名验证、过期时间验证、签发者验证、受众验证等。不要信任未经验证的JWT。

例如,在验证JWT时,除了验证签名和过期时间外,还可以验证签发者(iss)是否为预期的认证服务器,受众(aud)是否包含当前的资源服务器等。

六、JWT的常见问题与解决方案

6.1 JWT过期问题

JWT过期是一个常见的问题,当JWT的exp声明所指定的时间早于当前时间时,JWT将被视为无效。处理JWT过期问题的常见解决方案如下:

6.1.1 刷新令牌机制

如前所述,刷新令牌是一种长期有效的令牌,用于在JWT过期后获取新的JWT。客户端在JWT过期时,使用刷新令牌向认证服务器请求新的JWT,认证服务器验证刷新令牌的有效性后,生成新的JWT并返回给客户端。

实现刷新令牌机制的步骤如下:

  1. 生成刷新令牌:在用户登录成功后,除了生成JWT(访问令牌)外,还生成一个刷新令牌,并将刷新令牌存储在服务器端(如数据库、Redis等)。
  2. 客户端存储刷新令牌:客户端将刷新令牌存储在安全的位置。
  3. JWT过期时使用刷新令牌获取新JWT:当客户端发现JWT过期时,使用刷新令牌向认证服务器发送请求,获取新的JWT。
  4. 验证刷新令牌:认证服务器验证刷新令牌的有效性,如果有效,则生成新的JWT并返回;如果无效,则要求用户重新登录。
  5. 刷新令牌过期或失效:当刷新令牌过期或被撤销(如用户注销)时,客户端需要重新登录。

以下是实现刷新令牌机制的示例代码(基于Spring Boot):

// 生成访问令牌和刷新令牌
public class TokenResponse {
    private String accessToken;
    private String refreshToken;
    private long accessTokenExpiresIn;
    private long refreshTokenExpiresIn;
      // getter和setter方法
}
@Service
public class TokenService {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
     @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  // 访问令牌过期时间:30分钟
    private static final long ACCESS_TOKEN_EXPIRATION = 30 * 60 * 1000;
    // 刷新令牌过期时间:7天
    private static final long REFRESH_TOKEN_EXPIRATION = 7 * 24 * 60 * 60 * 1000;
 public TokenResponse generateTokens(UserDetails userDetails) {
        // 生成访问令牌
        String accessToken = jwtTokenUtil.generateToken(userDetails, ACCESS_TOKEN_EXPIRATION);
// 生成刷新令牌(可以使用UUID等生成唯一字符串)
        String refreshToken = UUID.randomUUID().toString();
  // 存储刷新令牌到Redis,设置过期时间
        redisTemplate.opsForValue().set(
                "refresh_token:" + refreshToken,
                userDetails.getUsername(),
                REFRESH_TOKEN_EXPIRATION,
                TimeUnit.MILLISECONDS
        );
     TokenResponse tokenResponse = new TokenResponse();
        tokenResponse.setAccessToken(accessToken);
        tokenResponse.setRefreshToken(refreshToken);
        tokenResponse.setAccessTokenExpiresIn(ACCESS_TOKEN_EXPIRATION);
        tokenResponse.setRefreshTokenExpiresIn(REFRESH_TOKEN_EXPIRATION);
  return tokenResponse;
    }
public TokenResponse refreshAccessToken(String refreshToken) {
        // 从Redis中获取用户名
        String username = (String) redisTemplate.opsForValue().get("refresh_token:" + refreshToken);
        if (username == null) {
            throw new InvalidRefreshTokenException("Invalid or expired refresh token");
        }
     // 加载用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
   // 生成新的访问令牌
        String newAccessToken = jwtTokenUtil.generateToken(userDetails, ACCESS_TOKEN_EXPIRATION);
  // 可以选择同时刷新刷新令牌(可选)
        // String newRefreshToken = UUID.randomUUID().toString();
        // redisTemplate.delete("refresh_token:" + refreshToken);
        // redisTemplate.opsForValue().set("refresh_token:" + newRefreshToken, username, REFRESH_TOKEN_EXPIRATION, TimeUnit.MILLISECONDS);
    TokenResponse tokenResponse = new TokenResponse();
        tokenResponse.setAccessToken(newAccessToken);
        tokenResponse.setRefreshToken(refreshToken); // 或使用新的刷新令牌
        tokenResponse.setAccessTokenExpiresIn(ACCESS_TOKEN_EXPIRATION);
        tokenResponse.setRefreshTokenExpiresIn(redisTemplate.getExpire("refresh_token:" + refreshToken, TimeUnit.MILLISECONDS));
  return tokenResponse;
    }
  public void revokeRefreshToken(String refreshToken) {
        redisTemplate.delete("refresh_token:" + refreshToken);
    }
}
6.1.2 提前刷新机制

客户端可以在JWT即将过期时(如过期前5分钟),主动向服务器请求新的JWT,以避免在请求过程中出现JWT过期的情况。这种方式可以提高用户体验,减少请求失败的概率。

客户端可以通过以下方式实现提前刷新机制:

  1. 解析JWT,获取其过期时间(exp)。
  2. 计算当前时间与过期时间的差值,如果差值小于设定的阈值(如5分钟),则主动请求新的JWT。
  3. 使用新的JWT替换旧的JWT,并继续发送请求。
6.2 JWT吊销问题

如前所述,JWT一旦生成,在过期前无法被主动吊销,这会带来一些安全问题。除了使用刷新令牌和黑名单机制外,还有以下解决方案:

6.2.1 短期JWT结合缓存

将JWT的过期时间设置得非常短(如1分钟),同时使用缓存存储用户的状态信息(如是否注销、权限是否变更等)。服务器在验证JWT时,除了验证JWT本身外,还会查询缓存中的用户状态信息,如果用户状态无效(如已注销),则拒绝该JWT。

这种方式的优点是可以快速吊销用户的访问权限,缺点是增加了服务器的缓存查询压力,且JWT过期时间过短可能会影响用户体验。

6.2.2 版本控制

为每个用户设置一个版本号,当用户需要被吊销(如注销、权限变更)时,更新用户的版本号。在生成JWT时,将用户的版本号包含在载荷中。服务器在验证JWT时,除了验证其他信息外,还会比较JWT中的版本号与数据库中用户的当前版本号,如果不一致,则拒绝该JWT。

这种方式的优点是实现简单,不需要维护黑名单,缺点是当用户版本号更新后,所有基于旧版本号生成的JWT都会失效,可能会影响正常用户的使用。

以下是使用版本控制的示例代码:

// 用户实体类
public class User {
    private String username;
    private String password;
    private int version; // 版本号
    // 其他字段和方法
}
// 生成JWT时包含版本号
public String generateToken(User user) {
    Map<String, Object> claims = new HashMap<>();
    claims.put("version", user.getVersion());
    // 其他声明
    return Jwts.builder()
            .setClaims(claims)
            .setSubject(user.getUsername())
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
            .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
            .compact();
}
// 验证JWT时检查版本号
public boolean validateToken(String token, User user) {
    Claims claims = extractAllClaims(token);
    int tokenVersion = (Integer) claims.get("version");
    return tokenVersion == user.getVersion() && !isTokenExpired(token);
}

当用户需要被吊销时,更新用户的版本号:

public void revokeUser(String username) {
    User user = userRepository.findByUsername(username);
    user.setVersion(user.getVersion() + 1);
    userRepository.save(user);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值