SpringBoot – SSO in BIRC

本文件透過商業智慧研究中心 – 智慧校園:活動系統來說明商智中心的SSO架構

商智中心的SSO為自架伺服器,Google SSO,但原理大同小異。

該文章所述程式碼皆提供於文章最下方GitHub中。

版本:0.1.0
最後更新時間:06/10/2026
最後更新人員:陳泓毓


概述

採用 授權碼(Authorization Code)交換 模式處理並驗證憑證:

角色說明
SSO 登入中心集中式身分驗證服務,負責 Google 等外部 IdP 登入、核發一次性授權碼
系統前端導向 SSO 登入、接收回傳的 code、轉交後端交換 JWT
系統後端Client Secret 向 SSO 驗證 code、建立/更新本地使用者、核發應用程式 JWT

設計原則: Client Secret 僅存於後端,前端永遠不應直接呼叫 SSO 的 /sso/verify-code。(避免被XSS)

JWT可參考: https://hyc.eshachem.com/program/291-2/


整體登入流程

sequenceDiagram
    participant User as 使用者
    participant FE as 前端應用
    participant SSO as SSO 登入中心
    participant BE as Campus Activity 後端
    participant DB as 資料庫

    User->>FE: 點擊「登入」
    FE->>SSO: GET /sso/prelogin?redirect_url=[前端回呼網址]
    SSO->>User: 顯示登入頁(Google 等)
    User->>SSO: 完成身分驗證
    SSO->>FE: 302 跳轉至 redirect_url?code=[一次性授權碼]
    FE->>BE: POST /api/auth-tokens/exchange<br/>(Header: X-Client-Token: code)
    BE->>SSO: POST /sso/verify-code<br/>(Header: X-Client-Id, X-Client-Secret | Body: code)
    SSO-->>BE: 傳回使用者資料 (email, name, picture)
    BE->>DB: 查詢或建立使用者
    BE-->>FE: 200 OK<br/>(Header: X-Auth-Token: JWT)
    FE->>BE: 後續 API 請求<br/>(Header: Authorization: Bearer JWT)

API說明

SSO 登入商智中心

端點方法說明
/sso/preloginGET發起登入,參數 redirect_url 為登入成功後的前端回呼網址
/sso/verify-codePOST以授權碼換取使用者資訊(僅限後端呼叫)

verify-code 請求格式:

POST {SSO_BASE_URL}/sso/verify-code
Content-Type: text/plain
X-Client-Id: {client-id}
X-Client-Secret: {client-secret}

{code}

成功回應範例:

{
  "email": "s12345678@ntub.edu.tw",
  "name": "王小明",
  "picture": "https://..."
}Code language: JSON / JSON with Comments (json)

失敗回應範例:

{
  "error": "invalid_or_expired_code"
}Code language: JSON / JSON with Comments (json)

系統後端處理

API:交換 Token

POST /api/auth-tokens/exchange
X-Client-Token: {SSO 回傳的 code}
項目說明
認證需求不需要 Bearer Token(公開端點)
請求 HeaderX-Client-Token:SSO 回傳的一次性授權碼
成功回應 HeaderX-Auth-Token:本系統核發的 JWT
成功回應 Body{ "message": "登入成功", ... }

實作位置:AuthController.exchangeToken()AuthServiceImpl.ssoLogin()

處理邏輯

  1. 驗證 code 不為空
  2. 向 SSO /sso/verify-code 發送 POST 請求
  3. 解析回應,取得 emailnamepicture
  4. schoolEmail 查詢本地使用者:
  • 已存在:同步更新 namepicture(若有變更)
  • 不存在:自動建立新使用者
  1. 以本地使用者資料產生 JWT,回傳給前端

    新使用者預設值

    欄位
    studentId取自 email @ 前的字串
    schoolEmailSSO 回傳的 email
    identityTEACHER_STUDENT(在職師生)
    statustrue(啟用)

    JWT 認證機制

    SSO 登入完成後,後續的 API 請求皆使用本系統自行核發的 JWT,與 SSO 無關。

    請求格式:

    Authorization: Bearer {JWT}Code language: HTTP (http)

    JWT Payload 欄位:

    Claim說明
    sub使用者學校信箱(schoolEmail
    id本地使用者 ID
    name顯示名稱
    picture頭像 URL
    roleSpring Security 角色,格式為 ROLE_{IdentityRole}
    host依信箱推斷的系所名稱
    iat / exp簽發時間 / 過期時間(預設 24 小時)

    相關元件:

    • JwtProvider:產生與驗證 JWT
    • JwtAuthenticationFilter:從 Authorization Header 解析 JWT,設定 Spring Security Context
    • SecurityConfig/auth-tokens/** 為公開端點,其餘端點由 Filter 處理認證

    環境參數

    設定檔:src/main/resources/application.yml

    sso:
      base-url: ${SSO_BASE_URL:https://sso.ntubimdbirc.tw}
      client-id: ${SSO_CLIENT_ID:campus-activity}
      client-secret: ${SSO_CLIENT_SECRET}
    
    application:
      security:
        jwt:
          secret-key: ${spring.security.jwt.secret}
          expiration: 86400000  # 24 小時(毫秒)Code language: YAML (yaml)
    環境變數說明預設值(範例)
    SSO_BASE_URLSSO 登入中心 Base URLhttps://sso.ntubimdbirc.tw
    SSO_CLIENT_ID向 SSO 註冊的 Client IDcampus-activity
    SSO_CLIENT_SECRETClient Secret(必填,勿提交Git
    spring.security.jwt.secretJWT 簽章金鑰(Base64,≥ 512 bits)見 application.yml

    安全提醒: SSO_CLIENT_SECRET 與 JWT Secret 應透過環境變數或密鑰管理服務注入,不可寫入前端程式碼或公開儲存庫。


    錯誤處理

    所有 SSO 相關錯誤由 SsoException 拋出,並由 ExceptionHandleController 統一回應:

    {
      "errorCode": "SSO_ERROR",
      "message": "{錯誤代碼}",
      "status": {HTTP 狀態碼}
    }Code language: JavaScript (javascript)
    messageHTTP 狀態說明
    missing_code400未提供 X-Client-Token
    unauthorized401SSO 回傳 invalid_or_expired_code(code 無效或已過期)
    bad_request400SSO 回傳其他錯誤
    sso_empty_response502SSO 回應為空
    sso_missing_info502SSO 回應缺少 email
    sso_response_parse_error502SSO 回應 JSON 解析失敗
    error_login502SSO 連線失敗或其他未預期錯誤

    前端整合文件

    詳情可參考: Next.js – SSO in BIRC ,這裡簡單做個介紹。

    發起登入

    const SSO_BASE_URL = 'https://sso.ntubimdbirc.tw';
    const redirectUrl = encodeURIComponent('https://your-frontend.example.com/callback');
    
    window.location.href = `${SSO_BASE_URL}/sso/prelogin?redirect_url=${redirectUrl}`;Code language: JavaScript (javascript)

    處理回呼

    SSO 登入成功後,會將使用者導向 redirect_url?code={授權碼}

    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get('code');
    const error = urlParams.get('error');
    
    if (error) {
      // 處理登入失敗
    } else if (code) {
      await exchangeToken(code);
      // 建議清除 URL 中的 code 參數
      window.history.replaceState({}, document.title, window.location.pathname);
    }Code language: JavaScript (javascript)

    向後端交換 JWT

    async function exchangeToken(code) {
      const response = await fetch('https://your-api.example.com/api/auth-tokens/exchange', {
        method: 'POST',
        headers: {
          'X-Client-Token': code,
        },
      });
    
      if (!response.ok) {
        throw new Error('登入失敗');
      }
    
      const jwt = response.headers.get('X-Auth-Token');
      // 儲存 JWT(建議使用 httpOnly cookie 或安全的 storage 策略)
      localStorage.setItem('authToken', jwt);
    }Code language: JavaScript (javascript)

    呼叫受保護 API

    const response = await fetch('https://your-api.example.com/api/...', {
      headers: {
        'Authorization': `Bearer ${localStorage.getItem('authToken')}`,
      },
    });Code language: JavaScript (javascript)

    CORS 注意事項

    後端已將 X-Auth-Token 加入 CORS exposedHeaders,前端可從 Response Header 讀取 JWT。若前端與後端跨域,請確認前端允許讀取此 Header。


    身分權限

    SSO 首次登入建立的使用者預設為 TEACHER_STUDENT。管理員角色需由系統另行指派。

    枚舉值代碼說明
    EXTERNAL0訪客
    TEACHER_STUDENT1在職師生(SSO 新使用者預設)
    ACTIVITY_ADMIN2活動管理員
    VENUE_ADMIN3場地管理員
    ADMIN4系統管理員

    JWT 中的 role claim 會帶有 ROLE_ 前綴,供 Spring Security 授權判斷使用。


    本地測試

    專案內提供模擬測試頁面(可於GitHub下載)

    此測試頁面示範完整 SSO 流程的前端行為。其中「步驟三」直接在前端呼叫 SSO verify-code 僅供開發除錯,正式環境應改為呼叫本後端的 /auth-tokens/exchange

    Swagger UI: 啟動後可於 /api 找到 Authentication 群組下的 POST /auth-tokens/exchange 端點進行測試。

    原始碼索引

    https://github.com/Chen11111112/SSO-in-BIRC-with-Spring-Boot.git

    檔案職責
    AuthController.javaSSO code 交換 API 端點
    AuthServiceImpl.javaSSO 驗證、使用者建立/更新、JWT 核發
    JwtProvider.javaJWT 產生與驗證
    JwtAuthenticationFilter.java請求攔截與 JWT 解析
    SecurityConfig.javaSpring Security 與 CORS 設定
    SsoException.javaSSO 錯誤例外
    ExceptionHandleController.java統一錯誤回應
    application.ymlSSO 與 JWT 設定