欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

jwt原理及Java中實現(xiàn)過程

 更新時間:2025年08月28日 08:53:09   作者:????洞察??????  
JWT是無狀態(tài)認證的JSON令牌,包含頭部、載荷和簽名,用于身份驗證與權限管理,需注意設置過期時間、使用非對稱算法、安全存儲傳輸,及合理刷新策略,確保系統(tǒng)安全與效率

一、JWT 是什么?解決什么問題?

  • 我們先來一張圖看一下這個過程:


JWT(JSON Web Token)是一種把“認證信息(Claims)+ 完整性校驗”打包成 自包含 的字符串的規(guī)范。

它主要用于無狀態(tài)認證:服務端驗證簽名即可信任其中的身份與權限,無需每次查庫或維護會話(session)。

  • 無狀態(tài):后端不存會話;減少分布式共享狀態(tài)的復雜度。
  • 可擴展:把自定義字段寫入 claims(如角色、租戶、權限)。
  • 可委托:不同服務/網(wǎng)關只要有驗證密鑰就能核驗并信任。

但請記?。篔WT 只保證 完整性(沒被篡改),默認不保密(除非用 JWE 加密)。敏感信息不要塞進未加密的 JWT。

二、JWT 的結構與簽名流程

JWT 的字符串形如:<header>.<payload>.<signature>

Header(JSON,Base64URL)

  • alg: 簽名算法(如 HS256 / RS256 / ES256 / EdDSA
  • typ: 通常為 "JWT"
  • kid(可選):密鑰標識,用于密鑰輪換。

Payload/Claims(JSON,Base64URL)

常見注冊聲明:

  • iss(頒發(fā)者)、sub(主體,通常是用戶ID)、aud(受眾)
  • exp(過期時間,秒級時間戳,必須?。?、nbf(不早于)、iat(簽發(fā)時間)
  • jti(唯一ID,用于一次性/黑名單)
    以及你的自定義字段rolestenantId、scope 等。

Signature

  • 計算方式:signature = Sign( base64url(header) + "." + base64url(payload), key, alg )
  • 驗證時:使用共享密鑰(HMAC)或公鑰(RSA/ECDSA/EdDSA)驗證。

JWS vs JWE

  • JWS(最常用):簽名但不加密;任何人拿到 token 都能看到 payload。
  • JWE:加密(可選),適用于含敏感信息的場景。

三、JWT 使用流程(最小閉環(huán))

  1. 登錄:用戶名密碼校驗成功 → 頒發(fā)短期 Access Token(JWT)+ 較長期 Refresh Token(不可見給前端或放 HttpOnly Cookie)。
  2. 訪問 API:前端將 Authorization: Bearer <jwt> 送給后端。
  3. 后端:驗證簽名、校驗 exp/nbf/aud/iss 等 → 放行。
  4. 刷新:Access Token 過期,用 Refresh Token 換新(做輪換失效控制)。
  5. 登出/撤銷(可選):把 jti 或 refresh 的標識加入黑名單,或進行密鑰輪換。

四、常見安全陷阱(一定要看)

  • ? 不設置 exp(永不過期,風險極大)。
  • ? alg: none(嚴格禁用)。
  • ? 密鑰混淆:把對稱密鑰誤當作公鑰發(fā)布;或同一 kid 指向錯密鑰。
  • ? HS256 在多服務擴散:一旦泄露,所有服務都可偽造??绶战ㄗh RS256/ES256/EdDSA(私鑰簽、公鑰驗)。
  • ? 不校驗 aud/iss:導致“錯配 token”被誤信任。
  • ? 客戶端 localStorage 存儲 → 易受 XSS 影響。推薦 HttpOnly + Secure + SameSite Cookie。
  • ? 忽視 CSRF:若用 Cookie 攜帶 Access Token,要配合 SameSite + CSRF 令牌 或改為 Bearer 頭。
  • ? 不做 Refresh Token 輪換 與黑名單 → 被盜后長期可用。
  • ? 把敏感信息(如身份證、銀行卡、密碼)塞進未加密 JWT。

五、Java 手寫(JJWT)創(chuàng)建與驗證

1) 依賴(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> <!-- for JSON serialization -->
  <scope>runtime</scope>
</dependency>

若用 RSA/EC/EdDSA:還需對應的 jjwt-xxx 或者用 java.security 生成密鑰。

2) 生成密鑰(示例:RSA 與 Ed25519)

// RSA 2048(推薦生產至少 2048)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair rsaKeyPair = kpg.generateKeyPair();

// Ed25519(更輕更快)
KeyPairGenerator ed = KeyPairGenerator.getInstance("Ed25519");
KeyPair edKeyPair = ed.generateKeyPair();

3) 頒發(fā) JWT(RS256 或 EdDSA)

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.time.Instant;
import java.util.Date;
import java.util.Map;

// 使用 RSA 私鑰簽名(RS256)
String token = Jwts.builder()
    .setHeaderParam("kid", "key-2025-08")     // 便于輪換
    .setIssuer("https://auth.example.com")
    .setSubject("user-123")
    .setAudience("api.example.com")
    .setIssuedAt(new Date())
    .setExpiration(Date.from(Instant.now().plusSeconds(900))) // 15 分鐘
    .addClaims(Map.of(
        "roles", new String[]{"ADMIN","USER"},
        "tenantId", "t-1001"
    ))
    .signWith(rsaKeyPair.getPrivate())        // 默認按密鑰類型選擇 RS256/ES256/EdDSA
    .compact();

signWith(PrivateKey):JJWT 會自動選合適 alg;如想強制算法,可用新版簽名 API 指定 SignatureAlgorithm.

4) 驗證 JWT(公鑰驗簽 + 校驗聲明)

import io.jsonwebtoken.*;

Jws<Claims> jws = Jwts.parserBuilder()
    .requireIssuer("https://auth.example.com")
    .requireAudience("api.example.com")
    .setAllowedClockSkewSeconds(60) // 允許 60s 時鐘偏差
    .setSigningKey(rsaKeyPair.getPublic())    // 或使用 JWKS 拉取的公鑰
    .build()
    .parseClaimsJws(token);

Claims claims = jws.getBody();
String userId = claims.getSubject();
String[] roles = claims.get("roles", String[].class);

校驗失敗會拋異常(如 ExpiredJwtExceptionSignatureException)。

在網(wǎng)關/過濾器中統(tǒng)一捕獲 → 返回 401/403。

六、Spring Boot(Resource Server)零膠水校驗

最省心的是讓 Spring Security 資源服務器替你做解析與校驗,它支持 JWK 集合(JWKS) 自動遠程拉取公鑰。

1) 依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

2) application.yml(通過 JWKS URL 校驗)

server:
  port: 8080

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://auth.example.com/.well-known/jwks.json
          issuer-uri: https://auth.example.com   # 建議同時配置,做 iss 校驗

你的授權服務器(自建或第三方,如 Auth0/Keycloak/Spring Authorization Server)對外暴露 JWKS。資源服自動緩存和輪詢kid 取公鑰。

3) 安全配置

@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
    http
      .csrf(csrf -> csrf.disable()) // 如果前端走 Bearer 頭,可關;若走 Cookie,需要保留并配置 CSRF
      .authorizeHttpRequests(reg -> reg
          .requestMatchers("/public/**").permitAll()
          .requestMatchers("/admin/**").hasRole("ADMIN")
          .anyRequest().authenticated()
      )
      .oauth2ResourceServer(oauth2 -> oauth2
          .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter()))
      );
    return http.build();
}

// 可選:把自定義 claims 映射為權限
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthConverter() {
    return jwt -> {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        List<String> roles = jwt.getClaimAsStringList("roles");
        if (roles != null) {
            roles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_" + r)));
        }
        return new JwtAuthenticationToken(jwt, authorities, jwt.getSubject());
    };
}

4) 控制器示例

@RestController
public class DemoController {

  @GetMapping("/me")
  public Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {
    return Map.of(
      "sub", jwt.getSubject(),
      "roles", jwt.getClaimAsStringList("roles"),
      "tenantId", jwt.getClaim("tenantId")
    );
  }

  @GetMapping("/admin/hello")
  public String admin() { return "hello, admin"; }
}

七、發(fā)行端:用 Spring Authorization Server 簽發(fā) JWT(概念位)

如果你既要頒發(fā)又要驗證

  • 引入 spring-authorization-server,配置客戶端、用戶認證、簽名密鑰(支持 RSA/ECDSA/EdDSA),開啟 /.well-known/jwks.json。
  • 認證成功后,框架自動頒發(fā) Access Token(JWT)Refresh Token。
  • 資源服務器只需 issuer-uri/jwk-set-uri 即可對接。

好處:密鑰管理、輪換、標準化授權流程(OAuth2/OIDC) 都交給框架;你專注業(yè)務。

八、Cookie vs Header、CSRF 與前端存儲

推薦Authorization: Bearer <jwt> 置于請求頭,前端保存在內存(刷新丟失)或安全容器;刷新策略依賴 HttpOnly Refresh Cookie。

若必須把 Access Token 放 Cookie

  • 設置:HttpOnly + Secure + SameSite=Lax/Strict;
  • 開啟并正確處理 CSRF 防護(基于 Cookie 的雙重提交策略或框架自帶 CSRF Token)。

不要放 localStorage(XSS 風險大)。

九、刷新與撤銷(實戰(zhàn)策略)

短期 Access Token(5–15 分鐘) + 長期 Refresh Token(7–30 天)

Refresh Token 輪換:每次刷新都頒發(fā)新 refresh,并使舊的失效(存庫并維護 revoked 標記或版本號)。

黑名單/撤銷

  • 記錄 Access Token 的 jti(可選)用于緊急撤銷;
  • 更常用的是縮短 Access Token壽命 + 輪換 Refresh;
  • 密鑰輪換:更換私鑰(新 kid),強制舊 token 逐步失效(需兼容一段時間,等舊 token 過期)。

十、微服務與網(wǎng)關

  • 首選:網(wǎng)關或每個服務自行校驗 JWT(拿到 JWKS 公鑰本地驗);不要把解析結果當作“可信 JSON”直接傳遞。
  • aud/iss:為不同受眾(微服務)使用不同 aud,防止“錯用 token”。
  • 性能:緩存 JWKS、公鑰對象;JWT 驗證成本很低,通常不是瓶頸。

十一、完整示例:無授權服務器時的“輕量頒發(fā) + 校驗”

1) 頒發(fā)端(登錄成功后)

// 假設你用 Spring Security 自己做用戶名/密碼認證
@PostMapping("/auth/login")
public Map<String, String> login(@RequestBody LoginReq req) {
    // 1. 校驗用戶名密碼(略)
    // 2. 頒發(fā) Token
    Instant now = Instant.now();
    String access = Jwts.builder()
        .setHeaderParam("kid", "key-2025-08")
        .setIssuer("https://auth.example.com")
        .setSubject("user-" + req.username())
        .setAudience("api.example.com")
        .setIssuedAt(Date.from(now))
        .setExpiration(Date.from(now.plusSeconds(900)))
        .claim("roles", List.of("USER"))
        .signWith(rsaPrivateKey) // 你的私鑰
        .compact();

    String refreshId = UUID.randomUUID().toString(); // 存入數(shù)據(jù)庫,標記有效
    String refresh = Jwts.builder()
        .setIssuer("https://auth.example.com")
        .setSubject("user-" + req.username())
        .setId(refreshId)
        .setIssuedAt(Date.from(now))
        .setExpiration(Date.from(now.plusSeconds(30 * 24 * 3600))) // 30 天
        .signWith(rsaPrivateKey)
        .compact();

    // refresh 建議放 HttpOnly Cookie 返回
    return Map.of("access_token", access, "token_type", "Bearer");
}

2) 刷新端點

@PostMapping("/auth/refresh")
public Map<String, String> refresh(@CookieValue("refresh_token") String refreshToken) {
    // 1. 驗證 refreshToken 簽名與過期
    Jws<Claims> jws = Jwts.parserBuilder()
        .setSigningKey(rsaPublicKey)
        .build()
        .parseClaimsJws(refreshToken);

    String jti = jws.getBody().getId();
    // 2. 校驗 jti 是否未吊銷,且未被使用(輪換)
    // 3. 頒發(fā)新 access(并輪換 refresh:生成新 refresh,舊的置為 revoked)

    String newAccess = ...;
    // Set-Cookie: refresh_token=<new>; HttpOnly; Secure; SameSite=Strict
    return Map.of("access_token", newAccess, "token_type", "Bearer");
}

3) 資源服務(校驗端,若不用 Resource Server Starter)

自定義過濾器(不建議重復造輪子,演示用):

@Component
public class JwtAuthFilter extends OncePerRequestFilter {

  private final PublicKey publicKey;

  public JwtAuthFilter(PublicKey publicKey) { this.publicKey = publicKey; }

  @Override
  protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
      throws ServletException, IOException {

    String auth = req.getHeader("Authorization");
    if (auth != null && auth.startsWith("Bearer ")) {
      String token = auth.substring(7);
      try {
        Jws<Claims> jws = Jwts.parserBuilder()
            .requireIssuer("https://auth.example.com")
            .requireAudience("api.example.com")
            .setAllowedClockSkewSeconds(60)
            .setSigningKey(publicKey)
            .build()
            .parseClaimsJws(token);

        Claims c = jws.getBody();
        List<GrantedAuthority> auths = new ArrayList<>();
        List<String> roles = c.get("roles", List.class);
        if (roles != null) roles.forEach(r -> auths.add(new SimpleGrantedAuthority("ROLE_" + r)));

        UsernamePasswordAuthenticationToken authentication =
            new UsernamePasswordAuthenticationToken(c.getSubject(), null, auths);

        SecurityContextHolder.getContext().setAuthentication(authentication);
      } catch (JwtException e) {
        res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return;
      }
    }
    chain.doFilter(req, res);
  }
}

十二、測試要點清單(上線前自查)

  • ? exp/nbf/iat/iss/aud 均有嚴格校驗;允許小量 clock skew。
  • ? 禁止 alg: none,不允許客戶端指定算法。
  • ? 使用 非對稱算法(RS/ES/EdDSA) 做跨服務驗證;對稱密鑰僅限單體/網(wǎng)關內部。
  • ? 開啟并演練 密鑰輪換kid + JWKS),舊公鑰保留到所有 token 過期。
  • ? 訪問控制基于 最小權限(角色/權限來源可在 claims 或 DB)。
  • ? 選擇合適的 存儲與傳輸方式(Bearer 頭 或 HttpOnly Cookie + CSRF 防護)。
  • ? 短期 Access + 輪換 Refresh;可選黑名單(jti)應急撤銷。
  • ? 日志中絕不打印完整 token(最多打前后各 6 位用于排錯)。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • 一次由Lombok的@AllArgsConstructor注解引發(fā)的錯誤及解決

    一次由Lombok的@AllArgsConstructor注解引發(fā)的錯誤及解決

    這篇文章主要介紹了一次由Lombok的@AllArgsConstructor注解引發(fā)的錯誤及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring?MVC?URL地址映射的示例代碼

    Spring?MVC?URL地址映射的示例代碼

    @RequestMapping是一個用來處理請求地址映射的注解,可用于類或方法上。用于類上,表示類中的所有響應請求的方法都是以該地址作為父路徑。,這篇文章主要介紹了Spring?MVC?URL地址映射,需要的朋友可以參考下
    2022-07-07
  • Java解析使用JSON的多種方法

    Java解析使用JSON的多種方法

    使用JSON作為數(shù)據(jù)傳輸,在瀏覽器端非常方便。JSON去除了所有JavaScript執(zhí)行代碼,只保留對象格式,而且JSON天生適合JavaScript處理,所以,絕大多數(shù)REST?API都選擇JSON作為數(shù)據(jù)傳輸格式?,F(xiàn)在問題來了:使用Java如何對JSON進行讀寫?
    2022-12-12
  • 淺談Java8 的foreach跳出循環(huán)break/return

    淺談Java8 的foreach跳出循環(huán)break/return

    這篇文章主要介紹了Java8 的foreach跳出循環(huán)break/return,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • maven安裝配置的實現(xiàn)步驟

    maven安裝配置的實現(xiàn)步驟

    本文主要介紹了maven安裝配置的實現(xiàn)步驟,包括下載和安裝Maven,配置Maven的環(huán)境變量,以及創(chuàng)建Maven項目,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • 基于多線程中join()的用法實例講解

    基于多線程中join()的用法實例講解

    下面小編就為大家?guī)硪黄诙嗑€程中join()的用法實例講解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • 如何使用Java讀取PPT文本和圖片

    如何使用Java讀取PPT文本和圖片

    這篇文章主要介紹了如何使用Java讀取PPT文本和圖片,本篇文章將介紹通過Java程序來讀取PPT幻燈片中的文本及圖片的方法。讀取圖片時,可讀取文檔中的所有圖片,也可以讀取指定幻燈片當中的圖片,需要的朋友可以參考下
    2019-07-07
  • Java中使用Jedis操作Redis的示例代碼

    Java中使用Jedis操作Redis的示例代碼

    本篇文章主要介紹了Java中使用Jedis操作Redis的示例代碼,具有一定的參考價值,有興趣的可以了解一下。
    2016-12-12
  • Java使用split分割無效獲取不到預期效果的解決辦法

    Java使用split分割無效獲取不到預期效果的解決辦法

    這篇文章主要給大家介紹了關于Java使用split分割無效獲取不到預期效果的解決辦法,java的String類中有個split方法,這個是我們經常使用到的,需要的朋友可以參考下
    2023-08-08
  • springboot jackson自定義序列化和反序列化實例

    springboot jackson自定義序列化和反序列化實例

    這篇文章主要介紹了spring boot jackson自定義序列化和反序列化實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評論