Loading...
正在加载...
请稍候

JWT认证详尽教程

QianXun (QianXun) 2025年10月17日 05:00
## 引言 JWT者,现代Web应用中常用之认证机制也,其以轻便、安全、跨域之特性,广受开发者青睐。本文旨在深入浅出,剖析JWT之原理、结构、应用场景及实现之道,辅以代码示例,务求读者明其理、践其行。 ## 一、JWT之本质 ### 1.1 何为JWT? JSON Web Token(JWT)乃一开放标准(RFC 7519),用于在各方间传递信息。其以JSON格式封装声明(claims),通过数字签名或加密确保数据之完整性与真实性。JWT常用于认证与授权,尤适于分布式系统、单点登录(SSO)及API安全。 ### 1.2 JWT之结构 JWT由三部分构成:**Header**、**Payload**、**Signature**,以`.`分隔,编码为Base64Url格式。其结构如下: ``` Header.Payload.Signature ``` #### 1.2.1 Header(头部) Header包含令牌之类型及签名算法,典型示例如下: ```json { "alg": "HS256", "typ": "JWT" } ``` - `alg`:签名算法,如HS256(HMAC-SHA256)、RS256(RSA-SHA256)等。 - `typ`:令牌类型,通常为`JWT`。 此JSON经Base64Url编码,形成JWT之第一部分。 #### 1.2.2 Payload(载荷) Payload承载声明(claims),即令牌之核心信息,分为三类: - **注册声明(Registered Claims)**:如`iss`(发行者)、`sub`(主体)、`exp`(过期时间)、`iat`(发行时间)等。 - **公开声明(Public Claims)**:自定义声明,需避免与注册声明冲突。 - **私有声明(Private Claims)**:应用专属声明,如用户ID、角色等。 示例: ```json { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1516242622 } ``` 此JSON亦经Base64Url编码,形成第二部分。 #### 1.2.3 Signature(签名) Signature通过对Header与Payload之Base64Url编码串,结合密钥(secret)及指定算法生成。HS256算法之签名公式如下: ``` HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) ``` 签名经Base64Url编码,形成第三部分,确保令牌未被篡改。 #### 1.2.4 完整JWT示例 假设Header、Payload及密钥如下: ```json // Header { "alg": "HS256", "typ": "JWT" } // Payload { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } // Secret "your-256-bit-secret" ``` 最终JWT为: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` ## 二、JWT之工作原理 ### 2.1 认证流程 1. **用户登录**:用户提交凭据(如用户名、密码),服务器验证后生成JWT。 2. **令牌下发**:服务器将JWT返回客户端,客户端存储之(通常于localStorage或Cookie)。 3. **请求认证**:客户端后续请求中,于HTTP头(通常`Authorization: Bearer <token>`)携带JWT。 4. **令牌验证**:服务器验证JWT之签名及有效性,确认用户身份及权限。 5. **响应处理**:若验证通过,服务器处理请求并返回结果;否则,拒绝访问。 ### 2.2 优势与局限 #### 优势: - **无状态**:JWT自包含用户信息,无需服务器存储会话。 - **跨域支持**:适用于分布式系统及微服务架构。 - **灵活性**:Payload可自定义,适配多种场景。 #### 局限: - **不可撤销**:JWT一旦签发,除非过期,难以主动失效。 - **数据暴露**:Payload仅Base64编码,非加密,敏感信息需谨慎存储。 - **令牌大小**:Payload过大可能导致请求头冗长。 ## 三、JWT之实现 以下以Node.js及`jsonwebtoken`库为例,展示JWT之生成与验证。 ### 3.1 环境准备 安装依赖: ```bash npm install jsonwebtoken ``` ### 3.2 生成JWT ```javascript const jwt = require('jsonwebtoken'); const user = { id: 123, name: 'John Doe' }; const secret = 'your-256-bit-secret'; const token = jwt.sign(user, secret, { expiresIn: '1h' }); console.log(token); ``` - `jwt.sign`:生成JWT。 - `expiresIn`:设置过期时间,如`1h`(1小时)。 ### 3.3 验证JWT ```javascript const jwt = require('jsonwebtoken'); const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; const secret = 'your-256-bit-secret'; try { const decoded = jwt.verify(token, secret); console.log(decoded); } catch (err) { console.error('Token验证失败:', err.message); } ``` - `jwt.verify`:验证签名及有效性,返回Payload或抛出错误。 ### 3.4 Express中间件 以下为Express中集成JWT认证之示例: ```javascript const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); const secret = 'your-256-bit-secret'; // 登录路由 app.post('/login', (req, res) => { // 模拟用户验证 const user = { id: 123, name: 'John Doe' }; const token = jwt.sign(user, secret, { expiresIn: '1h' }); res.json({ token }); }); // 受保护路由 app.get('/protected', (req, res) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: '无令牌' }); try { const decoded = jwt.verify(token, secret); res.json({ message: '访问成功', user: decoded }); } catch (err) { res.status(401).json({ error: '令牌无效' }); } }); app.listen(3000, () => console.log('服务器运行于3000端口')); ``` ## 四、JWT之最佳实践 1. **使用HTTPS**:确保JWT传输加密,防止拦截。 2. **短效令牌**:设置合理过期时间(如1小时),并结合刷新令牌(Refresh Token)延长会话。 3. **避免敏感信息**:Payload中勿存密码等敏感数据。 4. **强密钥**:使用复杂且安全的密钥,HS256需至少256位,RS256需公私钥对。 5. **令牌撤销**:维护黑名单(如Redis)存储失效JWT,或使用短效令牌。 6. **防御CSRF**:若使用Cookie存储JWT,需启用CSRF防护。 ## 五、常见问题与解决 1. **令牌过期**:捕获`TokenExpiredError`,提示用户重新登录或使用刷新令牌。 2. **签名篡改**:`jwt.verify`自动检测,抛出`JsonWebTokenError`。 3. **存储安全**:优先使用HttpOnly、Secure的Cookie存储JWT,防范XSS攻击。 ## 六、结语 JWT以其简洁高效,广泛应用于现代Web认证。然其安全依赖于正确配置与实践,开发者须谨慎处理密钥管理、令牌存储及防护措施。本教程以Node.js为例,然JWT之原理通用于各语言与框架,读者可依需适配。

讨论回复

1 条回复
QianXun (QianXun) #1
10-17 05:03
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JWT认证方式详尽教程</title> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet"> <style> :root { --primary-color: #1565c0; --primary-light: #e3f2fd; --secondary-color: #455a64; --accent-color: #ff5722; --background-color: #f5f7fa; --card-color: #ffffff; --text-color: #263238; --code-bg: #263238; --code-color: #aed581; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Noto Sans SC', sans-serif; background-color: var(--background-color); color: var(--text-color); line-height: 1.6; } .poster-container { width: 960px; min-height: 1600px; margin: 0 auto; padding: 40px; background: linear-gradient(135deg, #f5f7fa 0%, #e3f2fd 100%); position: relative; overflow: hidden; } .background-accent { position: absolute; width: 500px; height: 500px; border-radius: 50%; background: radial-gradient(circle, rgba(21, 101, 192, 0.1) 0%, rgba(21, 101, 192, 0) 70%); z-index: 0; } .accent-1 { top: -200px; right: -200px; } .accent-2 { bottom: -100px; left: -200px; } .content { position: relative; z-index: 1; } .header { text-align: center; margin-bottom: 40px; padding: 20px; background-color: var(--primary-color); color: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); } .header h1 { font-size: 48px; margin-bottom: 10px; letter-spacing: -0.5px; } .header p { font-size: 20px; opacity: 0.9; } .section { margin-bottom: 40px; background-color: var(--card-color); border-radius: 12px; padding: 30px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05); } .section-title { font-size: 32px; color: var(--primary-color); margin-bottom: 20px; display: flex; align-items: center; } .section-title .material-icons { margin-right: 10px; font-size: 32px; } .subsection { margin-bottom: 25px; } .subsection-title { font-size: 24px; color: var(--secondary-color); margin-bottom: 15px; display: flex; align-items: center; } .subsection-title .material-icons { margin-right: 8px; font-size: 24px; } p { font-size: 18px; margin-bottom: 15px; } ul, ol { margin-left: 25px; margin-bottom: 15px; } li { font-size: 18px; margin-bottom: 8px; } .highlight { background-color: var(--primary-light); padding: 2px 5px; border-radius: 4px; font-weight: 500; } .code-block { background-color: var(--code-bg); color: var(--code-color); padding: 20px; border-radius: 8px; font-family: 'Roboto Mono', monospace; font-size: 16px; overflow-x: auto; margin: 20px 0; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); } .code-block pre { margin: 0; white-space: pre-wrap; } .jwt-structure { display: flex; justify-content: space-between; margin: 20px 0; } .jwt-part { flex: 1; padding: 15px; margin: 0 5px; background-color: var(--primary-light); border-radius: 8px; text-align: center; } .jwt-part h4 { margin-bottom: 10px; color: var(--primary-color); } .jwt-flow { display: flex; justify-content: space-between; align-items: center; margin: 20px 0; } .flow-step { flex: 1; padding: 15px; background-color: var(--primary-light); border-radius: 8px; text-align: center; position: relative; } .flow-step:not(:last-child)::after { content: '\e5cc'; font-family: 'Material Icons'; position: absolute; right: -20px; top: 50%; transform: translateY(-50%); color: var(--primary-color); font-size: 24px; } .comparison-table { width: 100%; border-collapse: collapse; margin: 20px 0; } .comparison-table th, .comparison-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e0e0e0; } .comparison-table th { background-color: var(--primary-light); color: var(--primary-color); font-weight: 500; } .comparison-table tr:nth-child(even) { background-color: #f9f9f9; } .note { background-color: #fff8e1; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 0 8px 8px 0; } .note-title { font-weight: 500; color: #f57c00; margin-bottom: 5px; display: flex; align-items: center; } .note-title .material-icons { margin-right: 5px; font-size: 20px; } </style> </head> <body> <div class="poster-container"> <div class="background-accent accent-1"></div> <div class="background-accent accent-2"></div> <div class="content"> <header class="header"> <h1>JWT认证方式详尽教程</h1> <p>原理、架构、实现与最佳实践</p> </header> <section class="section"> <h2 class="section-title"> <i class="material-icons">info</i> JWT简介 </h2> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">help_outline</i> 什么是JWT? </h3> <p>JWT (JSON Web Token) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象安全地传输信息。这些信息可以被验证和信任,因为它是数字签名的。</p> <p>JWT通常用于身份验证和信息交换,特别是在分布式系统中,因为它具有无状态特性,不需要服务器存储会话信息。</p> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">compare_arrows</i> JWT与传统Session认证的区别 </h3> <table class="comparison-table"> <tr> <th>特性</th> <th>JWT认证</th> <th>Session认证</th> </tr> <tr> <td>状态存储</td> <td>无状态,信息存储在令牌中</td> <td>有状态,信息存储在服务器</td> </tr> <tr> <td>扩展性</td> <td>高,适合分布式系统</td> <td>低,需要共享会话存储</td> </tr> <tr> <td>跨域支持</td> <td>良好,可放在请求头中</td> <td>有限,依赖Cookie</td> </tr> <tr> <td>移动端支持</td> <td>良好</td> <td>有限,Cookie支持不完善</td> </tr> </table> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">sync_disabled</i> JWT的无状态特性 </h3> <p>JWT最大的特点是其无状态性,服务器不需要存储任何会话信息。每个请求都包含了足够的信息,使服务端能够验证用户。这种特性带来了以下优势:</p> <ul> <li>减轻服务器存储压力</li> <li>提高系统可用性和伸缩性</li> <li>更符合RESTful API的设计原则</li> <li>便于实现分布式系统</li> </ul> </div> </section> <section class="section"> <h2 class="section-title"> <i class="material-icons">architecture</i> JWT结构 </h2> <p>JWT由三部分组成,用点(.)分隔:Header.Payload.Signature</p> <div class="jwt-structure"> <div class="jwt-part"> <h4>Header</h4> <p>描述JWT的元数据</p> </div> <div class="jwt-part"> <h4>Payload</h4> <p>包含声明(Claims)</p> </div> <div class="jwt-part"> <h4>Signature</h4> <p>验证数据完整性</p> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">title</i> Header(头部) </h3> <p>Header通常由两部分组成:令牌的类型(typ)和所使用的签名算法(alg)。</p> <div class="code-block"> <pre><code class="json">{ "alg": "HS256", "typ": "JWT" }</code></pre> </div> <p>然后,这个JSON对象会被Base64Url编码,形成JWT的第一部分。</p> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">inventory_2</i> Payload(载荷) </h3> <p>Payload是JWT的主体部分,包含声明(Claims)。声明是关于实体(通常是用户)和其他数据的声明。</p> <p>声明分为三种类型:</p> <ul> <li><span class="highlight">注册声明(Registered Claims)</span>:预定义的一些声明,如iss(发行者)、exp(过期时间)、sub(主题)等</li> <li><span class="highlight">公共声明(Public Claims)</span>:自定义字段,可以用于交换信息</li> <li><span class="highlight">私有声明(Private Claims)</span>:自定义声明字段,只有JWT的创建者和使用者知晓</li> </ul> <div class="code-block"> <pre><code class="json">{ "sub": "1234567890", "name": "John Doe", "admin": true, "exp": 1516239022 }</code></pre> </div> <div class="note"> <div class="note-title"> <i class="material-icons">warning</i> 重要提示 </div> <p>Header和Payload只是经过Base64Url编码,并没有加密!任何人都可以解码它们并看到原始内容。因此,绝对不能在Payload中放置密码等敏感信息。</p> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">verified</i> Signature(签名) </h3> <p>Signature是对前两部分的签名,用于验证JWT的完整性,防止数据被篡改。</p> <p>签名的生成需要用到:</p> <ul> <li>编码后的Header</li> <li>编码后的Payload</li> <li>一个密钥(Secret)</li> <li>Header中指定的签名算法</li> </ul> <p>签名的计算公式如下:</p> <div class="code-block"> <pre><code class="text">HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )</code></pre> </div> <p>最终,将Header、Payload、Signature三个部分用点(.)连接起来,就构成了一个完整的JWT。</p> </div> </section> <section class="section"> <h2 class="section-title"> <i class="material-icons">account_tree</i> JWT工作流程 </h2> <div class="jwt-flow"> <div class="flow-step"> <h4>用户登录</h4> <p>提交用户名和密码</p> </div> <div class="flow-step"> <h4>生成JWT</h4> <p>验证成功后生成令牌</p> </div> <div class="flow-step"> <h4>存储JWT</h4> <p>客户端保存令牌</p> </div> <div class="flow-step"> <h4>携带JWT</h4> <p>每次请求携带令牌</p> </div> <div class="flow-step"> <h4>验证JWT</h4> <p>服务端验证令牌</p> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">login</i> 用户登录和令牌生成 </h3> <p>用户通过用户名和密码发起登录请求。服务器验证用户凭证,若验证成功,则使用JWT工具类生成令牌:</p> <ul> <li>Header:指定算法(如HS256)和令牌类型(JWT)</li> <li>Payload:包含用户信息(如用户ID、角色)和声明(如过期时间exp)</li> <li>Signature:使用密钥对Header和Payload进行签名,确保令牌不可篡改</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">save</i> 客户端存储令牌 </h3> <p>服务端将生成的JWT返回给客户端(通常通过响应体或Header)。客户端(如浏览器或移动端)将令牌存储在本地(如LocalStorage或Cookie)。</p> <div class="note"> <div class="note-title"> <i class="material-icons">security</i> 安全提示 </div> <p>建议将JWT存储在localStorage中,放在Cookie中会有CSRF风险。如果必须使用Cookie,应设置httpOnly和secure标志。</p> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">send</i> 请求携带令牌 </h3> <p>客户端在后续请求的Authorization Header中以Bearer格式携带JWT:</p> <div class="code-block"> <pre><code class="http">Authorization: Bearer &lt;JWT&gt;</code></pre> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">gpp_good</i> 服务端验证令牌 </h3> <p>服务器收到请求后,从请求头中提取JWT,并验证其合法性:</p> <ul> <li>签名验证:使用密钥校验签名是否有效</li> <li>过期检查:检查exp字段是否过期</li> <li>用户信息提取:解析Payload中的用户信息(如用户ID),用于后续权限控制</li> </ul> <p>若验证通过,服务端处理请求并返回数据;若验证失败(如令牌过期或签名错误),返回401状态码或自定义错误信息。</p> </div> </section> <section class="section"> <h2 class="section-title"> <i class="material-icons">balance</i> JWT优势与局限性 </h2> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">thumb_up</i> 优势 </h3> <ul> <li><span class="highlight">无状态性</span>:服务器不需要存储会话信息,减轻服务器压力</li> <li><span class="highlight">跨域支持</span>:JWT可以放在HTTP请求头中,轻松实现跨域认证</li> <li><span class="highlight">移动端友好</span>:不依赖Cookie,适合移动应用</li> <li><span class="highlight">避免CSRF攻击</span>:不使用Cookie,天然避免CSRF攻击</li> <li><span class="highlight">自包含</span>:JWT包含所有必要信息,减少数据库查询</li> <li><span class="highlight">可扩展</span>:适合分布式系统和微服务架构</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">thumb_down</i> 局限性 </h3> <ul> <li><span class="highlight">无法即时撤销</span>:JWT一旦签发,在过期前无法撤销</li> <li><span class="highlight">信息泄露风险</span>:Payload仅Base64编码,不应包含敏感信息</li> <li><span class="highlight">令牌体积大</span>:相比Session ID,JWT体积更大,增加网络开销</li> <li><span class="highlight">续签困难</span>:JWT过期时间固定,续签需要重新生成令牌</li> <li><span class="highlight">权限更新延迟</span>:用户权限变更后,需等待令牌过期才能生效</li> </ul> </div> </section> <section class="section"> <h2 class="section-title"> <i class="material-icons">security</i> JWT安全考虑 </h2> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">vpn_key</i> 密钥管理 </h3> <ul> <li>使用强密钥(至少256位)</li> <li>定期更换密钥</li> <li>不要将密钥硬编码在代码中</li> <li>使用环境变量或配置文件存储密钥</li> <li>考虑使用密钥管理服务</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">visibility_off</i> 敏感信息处理 </h3> <ul> <li>不要在Payload中存储敏感信息(如密码、信用卡号)</li> <li>如需传输敏感信息,考虑使用JWE(JSON Web Encryption)</li> <li>最小化Payload中的信息量</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">timer</i> 过期时间设置 </h3> <ul> <li>设置合理的过期时间(通常15分钟到几小时)</li> <li>对于长时间操作,使用刷新令牌机制</li> <li>考虑在用户活动时自动延长令牌有效期</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">enhanced_encryption</i> 算法选择 </h3> <ul> <li>避免使用'none'算法</li> <li>优先使用强算法如RS256(非对称)或HS256(对称)</li> <li>考虑使用ES256(ECDSA)以获得更小的签名</li> <li>根据安全需求选择合适的算法强度</li> </ul> </div> </section> <section class="section"> <h2 class="section-title"> <i class="material-icons">code</i> JWT实现示例 </h2> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">library_add</i> Maven依赖配置 </h3> <p>首先,在pom.xml文件中添加JWT相关依赖:</p> <div class="code-block"> <pre><code class="xml">&lt;!-- JWT Support --&gt; &lt;dependency&gt; &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt; &lt;artifactId&gt;jjwt-api&lt;/artifactId&gt; &lt;version&gt;0.12.3&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt; &lt;artifactId&gt;jjwt-impl&lt;/artifactId&gt; &lt;version&gt;0.12.3&lt;/version&gt; &lt;scope&gt;runtime&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt; &lt;artifactId&gt;jjwt-jackson&lt;/artifactId&gt; &lt;version&gt;0.12.3&lt;/version&gt; &lt;scope&gt;runtime&lt;/scope&gt; &lt;/dependency&gt;</code></pre> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">build</i> JWT工具类实现 </h3> <p>创建一个JWT工具类,用于生成和验证JWT令牌:</p> <div class="code-block"> <pre><code class="java">import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.util.Date; <span class="mention-invalid">@Component</span> public class JwtUtil { <span class="mention-invalid">@Value</span>("${jwt.secret}") private String jwtSecret; <span class="mention-invalid">@Value</span>("${jwt.expiration}") private int jwtExpirationMs; // 获取签名密钥 private SecretKey getSigningKey() { return Keys.hmacShaKeyFor(jwtSecret.getBytes()); } // 生成JWT令牌 public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpirationMs); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } // 从令牌中获取用户名 public String getUsernameFromToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); } // 验证令牌 public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token); return true; } catch (SignatureException ex) { System.err.println("Invalid JWT signature"); } catch (MalformedJwtException ex) { System.err.println("Invalid JWT token"); } catch (ExpiredJwtException ex) { System.err.println("Expired JWT token"); } catch (UnsupportedJwtException ex) { System.err.println("Unsupported JWT token"); } catch (IllegalArgumentException ex) { System.err.println("JWT claims string is empty."); } return false; } }</code></pre> </div> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">filter_alt</i> 拦截器/过滤器实现 </h3> <p>创建一个JWT过滤器,用于在Spring Security过滤器链中验证请求中的JWT:</p> <div class="code-block"> <pre><code class="java">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; <span class="mention-invalid">@Component</span> public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } <span class="mention-invalid">@Override</span> protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtil.getUsernameFromToken(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } }</code></pre> </div> </div> </section> <section class="section"> <h2 class="section-title"> <i class="material-icons">tips_and_updates</i> JWT最佳实践 </h2> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">storage</i> 令牌存储方式 </h3> <ul> <li>对于Web应用,推荐使用localStorage存储JWT</li> <li>如果使用Cookie,设置httpOnly和secure标志</li> <li>考虑使用短期访问令牌和长期刷新令牌的组合</li> <li>实现令牌黑名单机制,支持主动撤销</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">refresh</i> 刷新令牌机制 </h3> <ul> <li>使用短期访问令牌(如15分钟)和长期刷新令牌(如7天)</li> <li>刷新令牌应存储在安全的地方(如httpOnly Cookie)</li> <li>实现令牌刷新接口,在访问令牌过期时自动刷新</li> <li>考虑实现滑动会话,在用户活动时延长令牌有效期</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">admin_panel_settings</i> 权限控制 </h3> <ul> <li>在JWT中包含角色和权限信息</li> <li>实现基于角色的访问控制(RBAC)</li> <li>考虑实现细粒度的权限控制</li> <li>定期审查和更新用户权限</li> </ul> </div> <div class="subsection"> <h3 class="subsection-title"> <i class="material-icons">monitor_heart</i> 监控与日志 </h3> <ul> <li>记录JWT的生成、验证和失效事件</li> <li>监控异常的JWT使用模式</li> <li>实现令牌使用统计和分析</li> <li>设置警报,检测潜在的安全威胁</li> </ul> </div> </section> </div> </div> </body> </html>