S SmartDocs
系列: Springboot java 103 行 · 更新於 2026-08-14

JwtAuthenticationFilter.java

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

package com.example.bookstore.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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 java.io.IOException;

/**
 * JWT 認證過濾器——整個 JWT 機制的「守門員」。
 *
 * 【運作位置】
 * 它被插在 Spring Security 過濾器鏈(Filter Chain)中,
 * 「每一個」HTTP 請求進入 Controller 之前都會先經過這裡。
 *
 * 【工作流程】
 *   請求 → 取出 Header 中的 token → 驗證 → 合法就把使用者放入
 *   SecurityContext(本次請求的「已登入名單」)→ 放行給後續授權檢查
 *
 * 繼承 OncePerRequestFilter:保證同一請求只執行一次過濾邏輯。
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    /** Authorization Header 的標準前綴,例如:Authorization: Bearer eyJhb... */
    private static final String BEARER_PREFIX = "Bearer ";

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JwtService jwtService,
                                   UserDetailsService userDetailsService) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        // ---------- 步驟 1:從 Header 取出 Authorization ----------
        String authHeader = request.getHeader("Authorization");

        // 沒帶 token 或格式不對 → 直接放行。
        // 注意:「放行」不代表「允許存取」!後面的授權規則會判斷
        // 這個「匿名請求」能不能存取目標 API(不能就回 401)。
        if (authHeader == null || !authHeader.startsWith(BEARER_PREFIX)) {
            filterChain.doFilter(request, response);
            return;
        }

        // ---------- 步驟 2:去掉 "Bearer " 前綴,取得純 token ----------
        String token = authHeader.substring(BEARER_PREFIX.length());

        String username;
        try {
            // 解析 token 取得帳號;若 token 無效(簽章錯誤、過期、格式壞掉)
            // 會拋出例外
            username = jwtService.extractUsername(token);
        } catch (Exception e) {
            // token 無效 → 當作匿名請求放行,受保護的 API 之後自然會回 401
            filterChain.doFilter(request, response);
            return;
        }

        // ---------- 步驟 3:載入使用者並二次驗證 token ----------
        // 條件「SecurityContext 尚未有認證資訊」可避免重複處理
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            // 從資料庫載入使用者(含最新的角色權限)
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            if (jwtService.isTokenValid(token, userDetails)) {

                // ---------- 步驟 4:建立「已認證」物件並放入 SecurityContext ----------
                // 三個參數:使用者資料、憑證(JWT 模式不需要,給 null)、權限清單
                UsernamePasswordAuthenticationToken authToken =
                        new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());

                // 附加請求細節(來源 IP、Session ID 等),供稽核使用
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                // 關鍵一步!放入 SecurityContext 後,Spring Security
                // 就視這個請求為「已登入」,後續授權規則據此判斷角色權限
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        // ---------- 步驟 5:放行,讓請求繼續往下走 ----------
        filterChain.doFilter(request, response);
    }
}

相關文章

Springboot java EN 更新於 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).

閱讀文章 →
Springboot java EN 更新於 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).

閱讀文章 →
Springboot java EN 更新於 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).

閱讀文章 →
Springboot java EN 更新於 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).

閱讀文章 →
Springboot java EN 更新於 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).

閱讀文章 →
Springboot java EN 更新於 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).

閱讀文章 →