S SmartDocs
Serie: Springboot java 113 righe · Aggiornato 2026-08-14

JwtService.java

Springboot/bookstore-api/src/main/java/com/example/bookstore/security/JwtService.java

package com.example.bookstore.security;

import com.example.bookstore.entity.Role;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * JWT 服務:負責「產生」與「驗證」JSON Web Token。
 *
 * 【JWT 的結構】三段 Base64Url 字串,用「.」串接:
 *
 *   Header.Payload.Signature
 *   eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIn0.dBjftJeZ4CVP...
 *
 *   1. Header    : 宣告演算法,例如 {"alg":"HS256"}
 *   2. Payload   : 聲明(Claims),例如使用者帳號、角色、過期時間
 *   3. Signature : 用「伺服器私密金鑰」對前兩段計算出的簽章
 *
 * 【重要觀念】
 *   - Payload 只是 Base64 編碼,「任何人都能解讀」→ 千萬別放密碼等敏感資料!
 *   - 簽章能保證 token「沒有被竄改」:只要內容被改一個字元,
 *     簽章驗證就會失敗。而沒有金鑰的人無法偽造出合法簽章。
 */
@Service
public class JwtService {

    /**
     * 簽章用的密鑰字串,從 application.yml 的 app.jwt.secret 讀入。
     * HS256 演算法要求至少 256 bits(32 bytes),太短會直接拋出例外。
     */
    @Value("${app.jwt.secret}")
    private String secret;

    /** token 有效毫秒數,從 application.yml 的 app.jwt.expiration-ms 讀入 */
    @Value("${app.jwt.expiration-ms}")
    private long expirationMs;

    /** 把密鑰字串轉成 HMAC-SHA 演算法使用的 SecretKey 物件 */
    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 產生 JWT(登入成功後呼叫)。
     *
     * @param username 使用者帳號 → 放入標準聲明 sub(Subject,主體)
     * @param role     使用者角色 → 放入自訂聲明 "role"
     */
    public String generateToken(String username, Role role) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + expirationMs);

        return Jwts.builder()
                .subject(username)          // 標準聲明 sub:這個 token 屬於誰
                .claim("role", role.name()) // 自訂聲明:使用者角色
                .issuedAt(now)              // 標準聲明 iat:簽發時間
                .expiration(expiry)         // 標準聲明 exp:過期時間
                .signWith(getSigningKey())  // 用密鑰簽章(自動採用 HS256)
                .compact();                 // 組合成最終的字串
    }

    /**
     * 從 token 中解析出使用者帳號(sub 聲明)。
     * 若 token 無效(簽章錯誤、已過期、格式錯誤),parseClaims 會拋出例外。
     */
    public String extractUsername(String token) {
        return parseClaims(token).getSubject();
    }

    /**
     * 驗證 token 是否有效:
     *   1. 簽章正確(沒被竄改、確實是本伺服器簽發)
     *   2. 尚未過期
     *   3. token 中的帳號與資料庫載入的使用者一致
     */
    public boolean isTokenValid(String token, UserDetails userDetails) {
        try {
            Claims claims = parseClaims(token);
            boolean notExpired = claims.getExpiration().after(new Date());
            boolean sameUser = claims.getSubject().equals(userDetails.getUsername());
            return notExpired && sameUser;
        } catch (JwtException | IllegalArgumentException e) {
            // 簽章不符、過期、格式錯誤……一律視為無效
            return false;
        }
    }

    /**
     * 解析並驗證 token,回傳 Payload(Claims)。
     * verifyWith(key):解析時同步驗證簽章,驗證失敗會拋出 JwtException。
     */
    private Claims parseClaims(String token) {
        return Jwts.parser()
                .verifyWith(getSigningKey())  // 設定驗章金鑰
                .build()
                .parseSignedClaims(token)     // 解析 + 驗章 + 檢查過期
                .getPayload();
    }

    /** 提供 token 有效毫秒數(登入回應中告知客戶端) */
    public long getExpirationMs() {
        return expirationMs;
    }
}

Articoli correlati

Springboot java EN Aggiornato 2026-08-14

BookstoreApiApplication.java

BookstoreApiApplication.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/BookstoreApiApplication.java).

Leggi l'articolo →
Springboot java EN Aggiornato 2026-08-14

DataInitializer.java

DataInitializer.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/config/DataInitializer.java).

Leggi l'articolo →
Springboot java EN Aggiornato 2026-08-14

SecurityConfig.java

SecurityConfig.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/config/SecurityConfig.java).

Leggi l'articolo →
Springboot java EN Aggiornato 2026-08-14

AuthController.java

AuthController.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/controller/AuthController.java).

Leggi l'articolo →
Springboot java EN Aggiornato 2026-08-14

BookController.java

BookController.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/controller/BookController.java).

Leggi l'articolo →
Springboot java EN Aggiornato 2026-08-14

ApiErrorResponse.java

ApiErrorResponse.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/dto/ApiErrorResponse.java).

Leggi l'articolo →