知識與實務

商業實務・AI 應用・人才發展

AI 工作實務 · 技術解析

JWT 驗證通過,資料卻可能外洩:Spring Security 如何守住 API 的最後一道權限?

登入成功不等於資料可讀:從 SecurityFilterChain、Scope 到客戶歸屬與跨租戶負向測試,掌握 API 權限的最後一道門。

亮紫底上的 JWT 卡片通過驗證門,卻被資料授權門阻擋
本文目錄先看事故:同樣是業務角色,為什麼不能互看客戶?Spring Security 到底負責什麼?從進門到拿資料共有不同關卡JWT、OAuth 2.0 與 Spring Security,不要當成同一件事從一段設定看懂 SecurityFilterChain 的保護範圍@PreAuthorize 能保護方法,卻仍要確保資料邊界CSRF、CORS、Session:三個容易被 AI 生成程式碼誤處理的設定真正能交付的不是一段設定,而是正反向權限測試AI Agent 可以幫忙寫程式,但權限政策應由人定義上線前的最後檢核:把「有裝 Security」改成「能證明誰被擋下」

一位業務人員正常登入企業系統,帶著有效的 JWT 呼叫客戶資料 API。只要改掉網址中的客戶編號,竟然能看到另一位業務的客戶。這不是「沒有登入驗證」,而是系統只確認使用者的身分,沒有確認他能不能讀取這一筆資料。

Spring Security 能處理身分驗證、HTTP 請求授權與常見攻擊防護,但它無法憑空理解企業的客戶歸屬、部門隔離或簽核責任。要讓 Spring Boot REST API 真正安全,必須把登入、端點、業務操作、資料物件與測試串成同一條授權鏈。這篇文章以「內部客戶管理系統」為例,說明如何做出可驗收的安全邊界。

先記住一句話:JWT 證明的是請求者身分與權限宣告可被驗證,不代表他自動擁有每一筆資料的存取權。

先看事故:同樣是業務角色,為什麼不能互看客戶?

假設公司使用以下 API,查詢一筆客戶資料:

GET /api/customers/C-1024
Authorization: Bearer eyJ...(示意,不是真實權杖)

系統有兩位業務,Alice 負責 C-1024,Bob 負責 C-2088。兩人都有 sales 角色,都能使用「查詢客戶」功能。若系統只檢查 hasRole("SALES"),Bob 只需把網址改成 /api/customers/C-1024,就可能通過相同角色的檢查。

這是物件層級授權不足的典型情境。OWASP API Security Top 10 2023 將 Broken Object Level Authorization(BOLA)列為 API1:2023,說明問題不一定出在權杖無效,而是系統沒有核對「使用者與該資料物件的關係」[S01]。

別把「看不到前端按鈕」當成權限。前端介面可以隱藏功能,卻無法代替伺服器對每一次 API 請求做判斷。尤其企業系統有手機 App、報表服務、合作夥伴、AI Agent 等多種呼叫端,API 必須把所有入口視為需要驗證的請求。

Spring Security 到底負責什麼?從進門到拿資料共有不同關卡

可以把企業系統想成一棟辦公大樓。識別證是真的,僅代表進門者的身分可信;能否進財務室、調閱特定合約、修改付款金額,仍需要另外判斷。

Spring Security 的三個核心角色是:

層次它回答的問題在 Spring 應用中的典型位置
Authentication,身分驗證誰送來這個請求?權杖是否合法?JWT Decoder、Authentication、SecurityContext
Authorization,功能授權這個身分能不能呼叫此 API 或方法?SecurityFilterChain、authorizeHttpRequests、@PreAuthorize
Object authorization,資料授權他有權處理這個客戶、這張訂單或這間公司的資料嗎?Service 業務規則、帶租戶與擁有者條件的查詢

第三層不表示 Spring Security 沒用,而是它需要應用程式提供業務規則。例如同樣是主管,有些只能核准自己的部門,有些可以跨部門查看報表;這些規則不可能由套件自動猜出來。

在 Servlet 架構裡,請求會進入 Spring Security 的過濾器鏈。FilterChainProxy 根據匹配規則挑選 SecurityFilterChain,其中的過濾器依既定順序處理身分與權限,再決定是否繼續執行 Controller。若系統配置多條安全鏈,只有第一條匹配的安全鏈會被使用;配置順序與未覆蓋路徑都要測試[S02]。

JWT、OAuth 2.0 與 Spring Security,不要當成同一件事

在 Spring Boot 專案中,「幫 API 加 JWT」是一個常見需求,但三者的工作不同。

JWT 是一種可承載宣告的權杖格式;OAuth 2.0 是授權框架;Spring Security 則提供在應用程式內驗證身分與執行授權的機制。JWT 可以作為 OAuth 2.0 Bearer Access Token 的格式之一,但並非所有 OAuth 2.0 Token 都是 JWT,Spring Security 也支援不透明權杖(Opaque Token)[S03]。

當 API 扮演 Resource Server 時,Spring Security 可以根據發行者的公開金鑰驗證 JWT 簽章,並檢查到期時間、尚未生效時間與發行者等條件;還能把 scope 轉成具有 SCOPE_ 前綴的權限。若業務需要限定權杖只供指定 API 使用,還應設定 aud(Audience)驗證,而不是以為只設 issuer-uri 就自動驗完所有對象[S04]。

請特別區分驗證權杖與簽發權杖。Resource Server 的職責通常是驗證別人發出的 Token;它不是單靠新增 Spring Security 依賴就自動得到完整的登入頁面、身分供應者、金鑰管理、權杖撤銷和帳號生命週期。若企業已使用身分供應者,應先評估整合既有機制,避免重建不必要的帳號與金鑰流程。

從一段設定看懂 SecurityFilterChain 的保護範圍

以下是 Spring Boot Servlet REST API 的教學用片段,假設使用外部身分供應者簽發 JWT,並且 API 只接受從 Authorization 標頭攜帶 Bearer Token;不以瀏覽器自動附上的 Cookie 或 Session 做驗證。範例不是可直接部署的完整專案,跨網域來源、安全記錄、錯誤回應、資料權限仍需補齊。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.Customizer;

@Configuration
@EnableMethodSecurity
public class ApiSecurityConfig {

    @Bean
    SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // 僅適用於不依賴 Cookie 自動驗證的 Bearer-only API。
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(HttpMethod.GET, "/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
                .requestMatchers(HttpMethod.GET, "/api/customers/**")
                    .hasAuthority("SCOPE_customers:read")
                .anyRequest().denyAll())
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults()));

        return http.build();
    }
}

這段設定刻意把未列出的 API 預設拒絕。開發團隊每新增一支 API,就必須明確決定要讓誰通過,而不是讓新路徑意外繼承過寬的授權。hasAuthority("SCOPE_customers:read") 對應 Spring Security 預設將 JWT Scope 轉換為 Authority 的常見行為;如果企業身分供應者使用其他 Claim 或自訂角色格式,需另外設定權限轉換[S04][S05]。

對於範例的 application.yml,還需要指定發行者與預期受眾:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://idp.example.com/issuer
          audiences: https://crm-api.example.com

上面的網域純屬示意。實際環境必須指向可信的身分供應者,檢查其 Discovery/JWKS、金鑰輪替與組織的 Token 政策。依採用的 Spring Boot 版本管理相依套件,不要把網路上不同世代的 Java DSL 直接拼接使用。本文查核時,Spring 官方列出的最新穩定版為 Spring Security 7.1.2;7.2.0-RC1 是候選釋出版本,並非正式穩定版[S06][S07]。

範例最重要的限制:通過 URL 還是不能直接回傳資料

上述設定只能確認 Bob 有 customers:read 功能權限。若他試圖讀取 Alice 負責的 C-1024,還需要在 Service 與資料查詢層確認客戶歸屬及租戶邊界。

對使用 JPA 的系統,一種實務做法是將「可見資料範圍」放入 Repository 查詢條件,例如 findByIdAndTenantIdAndOwnerId(...),再由 Service 以已驗證的身分對應出可信的租戶及擁有者資訊。不要把前端傳來的 tenantId 或 ownerId 當成唯一的授權依據;對多人共同管理的客戶,則應以指派關係或 ACL 規則取代簡單的擁有者比對。

值得注意的是,管理者跨部門查閱、委派代理、共同編輯與歷史資料移交,都可能使「資源 owner 等於當前 user」過度簡化。權限模型要根據真正的業務關係設計,不能只因為它容易寫成一行 SQL 就當作正確規則。OWASP 特別提醒,單純拿 Token 中的使用者 ID 與請求參數比較,只能涵蓋 BOLA 的少部分情境[S01]。

@PreAuthorize 能保護方法,卻仍要確保資料邊界

@EnableMethodSecurity 可以啟用方法層級的授權判斷,透過 @PreAuthorize 在呼叫 Service 方法前檢查權限。例如:

@PreAuthorize("hasAuthority('SCOPE_customers:read')")
public CustomerDto getCustomer(String customerId) {
    // 這裡仍需依已驗證身分檢查租戶與資料歸屬。
    // 實務上以服務層或查詢條件實施物件層授權。
    return customerService.loadVisibleCustomer(customerId);
}

這是教學片段,不包含 CustomerDto、customerService 的完整實作。關鍵不在於多一個註解,而在於方法是否有一致的授權語義。Spring 官方也說明,Spring Boot Starter Security 不會預設替你啟用方法層級授權,必須明確啟用及測試[S08]。

此外,若你在同一個物件內自行呼叫標有 @PreAuthorize 的方法,代理式方法安全可能無法依預期攔截該內部呼叫;不要把安全性全部建立在未經測試的註解上。跨服務的資料存取更應將核心限制放到穩定的業務邏輯與資料查詢邊界,避免開發者從另一條程式路徑繞開檢查[S12]。

CSRF、CORS、Session:三個容易被 AI 生成程式碼誤處理的設定

開發者常見的求助是:「前端一直報 403,能不能把 CSRF 和 CORS 都關掉?」這往往會把不同問題混在一起。

CSRF 的核心風險,是攻擊者利用瀏覽器自動攜帶的認證資訊,誘使已登入的使用者送出非預期請求。對只接受 Authorization: Bearer 標頭、且沒有依賴瀏覽器自動攜帶憑證的 API,可在完整威脅模型下評估停用 CSRF;但若採 Cookie 驗證、Session 或其他自動附上的憑證,STATELESS 這個字或 JSON 格式本身都不代表不會發生 CSRF[S09]。

CORS 管的是瀏覽器跨來源存取政策,不是誰可以查某一筆客戶資料。即使允許某個前端網站跨域呼叫,API 仍須驗證 Token 與資源權限。遇到跨來源瀏覽器呼叫時,還需處理預檢請求(Preflight),並按確定的來源、HTTP 方法與標頭配置,不應為解決錯誤而不分情境全部放行[S10]。

Session 則與伺服器是否保存使用者狀態有關。SessionCreationPolicy.STATELESS 適合某些 Token API 的設計,但它不會自動替你解決 Token 遺失、撤銷、過度有效期、金鑰管理或操作稽核。前端保存 Token 的方式也必須納入 XSS、裝置遺失及會話政策的評估。

把三者的定義分開,才知道該修的是跨域設定、請求防偽、身分憑證,還是資料授權。

真正能交付的不是一段設定,而是正反向權限測試

企業在請 AI Agent 產生安全設定後,不應只用「能登入成功」作為驗收。至少建立以下最小測試矩陣,並把它放進 CI。

測試情境預期結果驗證的邊界
沒有 Token,呼叫受保護的客戶 API通常為 401身分驗證
簽章錯誤、過期或發行者不符的 Token通常為 401Token 驗證
身分有效但缺少 customers:read Scope通常為 403功能授權
Bob 有 Scope,卻查詢 Alice 的客戶拒絕;可依資源隱匿策略回 404物件授權
不同租戶使用者查詢相同格式的資源 ID不得跨租戶看到資料租戶隔離
主管有核准權,但試圖超越其核准額度拒絕,且不改變交易狀態業務規則
新增一支未宣告的敏感 API預設拒絕預設權限邊界

401、403 與 404 的實際回應會受 SecurityFilterChain、例外處理器及資源隱匿政策影響;表格是常見且合理的驗收預期,不能替代實際專案規格。某些系統選擇對無權查看的物件回 404,以避免透露資源存在與否,必須維持一致的處理策略。

Spring Security 提供 MockMvc 的 JWT 測試協助功能,可以用模擬身分與 Scope 檢查控制器及方法授權;但這類模擬測試不等於已驗證真實 JWT 簽章、發行者金鑰、部署環境或企業資料隔離。因此仍需有不同權杖、不同租戶、真實資料查詢路徑與整合測試[S11]。

這裡有一個可以重複使用的驗收問題:「如果我是一個合法使用者,只修改 URL 中的資源 ID、部門 ID 或租戶 ID,會發生什麼事?」 若團隊沒有明確測試過答案,就不能只憑成功登入宣稱權限完成。

AI Agent 可以幫忙寫程式,但權限政策應由人定義

在企業 AI Coding 流程中,可以把工作拆成三個明確交付物:政策表、程式變更、拒絕案例的測試證據。這比單純下令「幫我加入 JWT 安全」更有用。

第一步,業務負責人與系統分析師定義誰可以看、建立、修改、核准、刪除哪些資料,包含代管關係與跨部門例外。第二步,工程師讓 AI Agent 依現有版本、相依套件、身分供應者及 API 契約提出最小變更,避免自行建立不必要的登入和金鑰系統。第三步,Reviewer 要求 Agent 提供正反測試、受影響端點清單與可回滾的 Pull Request,而不是看見「測試全綠」就略過規格對照。

對 AI Agent 可以呼叫資料庫或內部工具的系統,這一點更重要。模型可能替某位使用者提出查詢,但AI 的工具身分不應成為繞過原始使用者資料權限的捷徑。需要根據使用者、任務與工具的不同身分設計授權與稽核,尤其是匯出大量資料、查詢薪資、修改合約與財務核准等情境。

上線前的最後檢核:把「有裝 Security」改成「能證明誰被擋下」

一套可交付的 Spring Security 專案,至少要能回答:哪些路徑公開、哪些必須登入、Scope/Role 如何映射、誰能存取哪筆資料、跨租戶如何隔離、拒絕時回什麼狀態碼、誰保存稽核資訊、憑證如何輪替,以及哪些負向測試已自動執行。

這些問題不必一次建成龐大的零信任平台。可以先挑一支最敏感的客戶或訂單 API,完成資料歸屬規則、預設拒絕、身分來源與跨租戶測試,再推廣到其他端點。當程式碼、政策表與拒絕測試同步維護時,Spring Security 才從套件依賴真正變成企業可驗收的安全能力。

進一步閱讀:Spring Boot 與企業後端架構;API 契約與 AI Coding 協作。前者提供整體架構背景,後者協助把授權錯誤與 API 行為寫進介面契約。

參考資料與查核範圍

本文依 Spring 官方文件及 OWASP 官方資料整理,查核日期為 2026 年 11 月 2 日;文中 Alice/Bob、客戶編號、API、網域及程式碼皆為教學情境,不代表真實客戶事故,也未宣稱在正式環境執行整合測試。

圖片與素材說明

本文封面為 AI 生成之原創概念插畫,呈現身分驗證與資料授權的不同關卡,並非真實產品介面或系統測試畫面。

AI 生成內容

本文由 TARAISG(TIANDIREN AI SOLUTION GROUP)使用 AI 模型產生正文、整理來源並設計實務工具。方法依據與限制列於文末;案例為明確標示的教學假設。未有具名紀錄時,不宣稱經人工或專家審核。

查看內容製作與更正說明

立即領!558元學習投資折抵金

能力投資季。立即領取 558 元學習金!全站線上課立即折抵。只需填寫 Email,即可輕鬆獲得,先領起來,不要錯過!

立即領!559元學習投資折抵金

能力投資季。立即領取 559 元學習金!全站線上課立即折抵。只需填寫 Email,即可輕鬆獲得,先領起來,不要錯過!

立即領!579 元學習金

學習,看見新可能。 10/14 前領取 579 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!599 元學習金

學習,帶來新可能。 09/09 前領取 558 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!558 元學習金

學習,發現新大陸。 8/29 前領取 558 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!559 元學習金

學習,連結新亮點。 8/14 前領取 559 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!555 元學習金

學習,發現更多視角。 6/14 前領取 555 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!556 元學習金

學習,累積無限可能。 5/29 前領取 556 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!557 元學習金

學習,讓我們發現未曾想像的可能!5/14 前領取 557 元折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

立即領!579 元折抵金

知識就是力量,專業成就未來!3/18 前領取 579 元課程折抵金,解鎖數百堂高品質線上課程,提升職場競爭力與專業技能。只需填寫Email,即可輕鬆獲得,投資自己從現在開始!

年終學習金

年終之餘用學習投資自己!填入 Email ,立即領取 588 元年終學習金!數百堂線上課程立即折抵,先領取收藏起來!