一、JWT 驗證方式詳解
JWT(JSON Web Token)的驗證核心是確保令牌未被篡改且符合業務規則,主要分為以下步驟:
1. 令牌解析與基礎校驗
收到客戶端傳遞的 JWT 后,首先按 .
分割為三部分:Header
、Payload
、Signature
。
Header
:解析算法(如HS256
)和令牌類型(固定為JWT
)。Payload
:解析標準聲明(如exp
過期時間、iat
簽發時間)和自定義聲明(如用戶 ID)。
2. 簽名驗證(防篡改)
簽名是 JWT 的核心安全屏障。驗證邏輯:
- 使用
Header
中指定的算法(如 HMAC SHA256),用服務端保存的密鑰對Header.Base64UrlEncode() + "." + Payload.Base64UrlEncode()
重新計算簽名。 - 對比新計算的簽名與 JWT 中的
Signature
,若不一致則令牌無效(可能被篡改)。
3. 聲明校驗(業務規則)
驗證 Payload
中的聲明是否符合業務要求:
exp
(Expiration Time):令牌過期時間戳,需滿足當前時間 < exp
(考慮時鐘偏差,如 ±300秒)。iat
(Issued At):令牌簽發時間,需滿足iat ≤ 當前時間
。iss
(Issuer):令牌簽發者,需與服務端配置的簽發者(如https://your-domain.com
)一致。aud
(Audience):令牌接收方,需與當前服務身份(如user-service
)匹配。- 自定義聲明:如用戶角色(
role
)、權限(permissions
)等,按業務需求驗證。
4. 令牌狀態校驗(可選)
對于需要主動失效的令牌(如用戶注銷),需維護一個黑名單(如 Redis),驗證時檢查令牌是否在黑名單中(適用于需要嚴格控制令牌生命周期的場景)。
二、后端 Java 示例(Spring Boot + JJWT)
以下是基于 Spring Boot 的 JWT 生成與驗證接口實現,使用 JJWT 庫(Java JWT 工具)。
1. 依賴配置
在 pom.xml
中添加 JJWT 依賴:
<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>
2. JWT 工具類(核心邏輯)
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Component
public class JwtUtils {// 從配置文件讀取密鑰和過期時間(示例:密鑰為 "your-256-bit-secret",過期時間 30分鐘)@Value("${jwt.secret}")private String secret;@Value("${jwt.expire}")private long expire;// 生成 JWTpublic String generateToken(String userId) {Date now = new Date();Date expireDate = new Date(now.getTime() + expire * 1000); // 轉換為毫秒Map<String, Object> claims = new HashMap<>();claims.put("userId", userId); // 自定義聲明claims.put("role", "user"); // 自定義角色return Jwts.builder().setClaims(claims) // 自定義聲明.setIssuer("your-domain") // 簽發者(iss).setIssuedAt(now) // 簽發時間(iat).setExpiration(expireDate) // 過期時間(exp).signWith(SignatureAlgorithm.HS256, secret) // 簽名算法+密鑰.compact();}// 驗證 JWT 并解析聲明public Claims validateToken(String token) {try {return Jwts.parser().setSigningKey(secret) // 使用相同密鑰驗證.parseClaimsJws(token) // 解析 JWT.getBody();} catch (ExpiredJwtException e) {throw new RuntimeException("令牌已過期");} catch (UnsupportedJwtException e) {throw new RuntimeException("不支持的令牌類型");} catch (MalformedJwtException e) {throw new RuntimeException("令牌格式錯誤");} catch (SignatureException e) {throw new RuntimeException("簽名驗證失敗(可能被篡改)");} catch (IllegalArgumentException e) {throw new RuntimeException("令牌為空");}}
}
3. 接口示例(生成與驗證)
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;@RestController
@RequestMapping("/auth")
public class AuthController {@Resourceprivate JwtUtils jwtUtils;// 模擬登錄接口:返回 JWT@PostMapping("/login")public String login(@RequestParam String username, @RequestParam String password) {// 實際業務中需校驗用戶名密碼(示例直接通過)return jwtUtils.generateToken("123"); // 用戶 ID 為 123}// 驗證令牌接口(示例)@PostMapping("/validate")public String validate(@RequestHeader("Authorization") String token) {// 提取 Bearer 令牌(去掉 "Bearer " 前綴)String actualToken = token.replace("Bearer ", "");try {Claims claims = jwtUtils.validateToken(actualToken);return "驗證成功!用戶 ID:" + claims.get("userId");} catch (Exception e) {return "驗證失敗:" + e.getMessage();}}
}
三、前端/客戶端示例(Uniapp、WPF、Qt)
以下示例演示客戶端如何攜帶 JWT 調用后端驗證接口(假設后端地址為 http://localhost:8080
)。
1. Uniapp(Vue 跨端框架)
<template><view><button @click="login">登錄獲取 Token</button><button @click="validateToken">驗證 Token</button><text>{{ result }}</text></view>
</template><script>
export default {data() {return {token: null,result: ""};},methods: {// 登錄獲取 Tokenasync login() {const res = await uni.request({url: "http://localhost:8080/auth/login",method: "POST",data: { username: "test", password: "123" }});this.token = res.data;this.result = "Token 獲取成功:" + this.token;},// 驗證 Token(攜帶到請求頭)async validateToken() {if (!this.token) {this.result = "請先登錄獲取 Token";return;}const res = await uni.request({url: "http://localhost:8080/auth/validate",method: "POST",header: { Authorization: `Bearer ${this.token}` }});this.result = res.data;}}
};
</script>
2. WPF(.NET 桌面應用)
using System;
using System.Net.Http;
using System.Windows;namespace WpfJwtDemo {public partial class MainWindow : Window {private string _token;private readonly HttpClient _httpClient = new HttpClient();public MainWindow() {InitializeComponent();_httpClient.BaseAddress = new Uri("http://localhost:8080/");}// 登錄獲取 Tokenprivate async void LoginButton_Click(object sender, RoutedEventArgs e) {var formData = new FormUrlEncodedContent(new[] {new KeyValuePair<string, string>("username", "test"),new KeyValuePair<string, string>("password", "123")});var response = await _httpClient.PostAsync("auth/login", formData);_token = await response.Content.ReadAsStringAsync();ResultText.Text = "Token獲取成功:" + _token;}// 驗證 Token(攜帶到請求頭)private async void ValidateButton_Click(object sender, RoutedEventArgs e) {if (string.IsNullOrEmpty(_token)) {ResultText.Text = "請先登錄獲取 Token";return;}_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);var response = await _httpClient.PostAsync("auth/validate", null);var result = await response.Content.ReadAsStringAsync();ResultText.Text = result;}}
}
3. Qt(C++ 跨平臺框架)
#include <QApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QMessageBox>class JwtDemo : public QObject {Q_OBJECT
public:JwtDemo(QObject *parent = nullptr) : QObject(parent) {manager = new QNetworkAccessManager(this);connect(manager, &QNetworkAccessManager::finished, this, &JwtDemo::onRequestFinished);}// 登錄獲取 Tokenvoid login() {QUrl url("http://localhost:8080/auth/login");QUrlQuery query;query.addQueryItem("username", "test");query.addQueryItem("password", "123");QNetworkRequest request(url);request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");manager->post(request, query.toString(QUrl::FullyEncoded).toUtf8());}// 驗證 Token(攜帶到請求頭)void validateToken(const QString &token) {QUrl url("http://localhost:8080/auth/validate");QNetworkRequest request(url);request.setRawHeader("Authorization", "Bearer " + token.toUtf8());manager->post(request, ""); // 空 body}private slots:void onRequestFinished(QNetworkReply *reply) {if (reply->error() == QNetworkReply::NoError) {QString result = reply->readAll();if (reply->url().path() == "/auth/login") {currentToken = result;QMessageBox::information(nullptr, "成功", "Token獲取成功:" + result);} else if (reply->url().path() == "/auth/validate") {QMessageBox::information(nullptr, "驗證結果", result);}} else {QMessageBox::critical(nullptr, "錯誤", "請求失敗:" + reply->errorString());}reply->deleteLater();}private:QNetworkAccessManager *manager;QString currentToken;
};int main(int argc, char *argv[]) {QApplication app(argc, argv);JwtDemo demo;// 模擬點擊登錄(實際需綁定UI事件)demo.login();// 假設登錄后驗證(實際需等待登錄完成)QTimer::singleShot(2000, [&demo]() {if (!demo.currentToken.isEmpty()) {demo.validateToken(demo.currentToken);}});return app.exec();
}#include "main.moc" // 需包含 moc 文件(Qt 元對象編譯)
四、關鍵說明
- 密鑰安全:后端密鑰(
jwt.secret
)需嚴格保密,避免硬編碼在代碼中(建議通過配置中心或環境變量獲取)。 - 過期時間:
exp
需根據業務場景設置(如用戶登錄態建議 30分鐘~1天,敏感操作建議更短)。 - 客戶端處理:前端需將 JWT 存儲在
localStorage
(Web)、SecureStorage
(移動端)或注冊表
(桌面端)中,避免明文存儲。 - 跨域問題:若前端與后端不同域,需在后端配置 CORS(跨域資源共享)。