forked from alterminal/alterminal
47 lines
1.7 KiB
Go
47 lines
1.7 KiB
Go
// Package oidc 實作 OIDC 端點:/.well-known/jwks.json,以及之後的
|
||
// Discovery、/authorize、/token 等,供 RP(Application)整合。
|
||
package oidc
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
|
||
"gorm.io/gorm"
|
||
|
||
"alterminal/internal/auth"
|
||
"alterminal/internal/jwk"
|
||
)
|
||
|
||
// jwksMaxAge 為 JWKS 回應的建議快取秒數。金鑰輪替流程為「先產生並
|
||
// 發佈新金鑰,舊金鑰退休前仍留在 JWKS 供已簽發的 token 驗證」,因此
|
||
// RP 快取一小時並不影響驗證:快取期間內新舊金鑰皆可取得。
|
||
const jwksMaxAge = 3600
|
||
|
||
// JWKSHandler 處理 GET /.well-known/jwks.json(RFC 7517 §5):發佈所有
|
||
// 使用中簽章金鑰的公開 JWK,供 RP 驗證 ID Token/Access Token 的
|
||
// 簽章。已退休金鑰不再發佈;無使用中金鑰時回應空的 keys 陣列。
|
||
func JWKSHandler(db *gorm.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
var keys []jwk.SigningKey
|
||
if err := db.Where("retired_at IS NULL").Order("created_at DESC").Find(&keys).Error; err != nil {
|
||
log.Printf("jwks: %v", err)
|
||
auth.WriteError(w, http.StatusInternalServerError, "內部錯誤")
|
||
return
|
||
}
|
||
set := jwk.JWKS{Keys: make([]jwk.JWK, 0, len(keys))}
|
||
for i := range keys {
|
||
k, err := keys[i].PublicJWK()
|
||
if err != nil {
|
||
// 單一金鑰的私鑰儲存毀損時跳過該金鑰並記錄待查,不讓整個
|
||
// 端點失靈——其餘金鑰照常發佈,RP 仍可驗證其簽發的 token。
|
||
log.Printf("jwks: 金鑰 %d(kid=%s)無法轉為公開 JWK: %v", keys[i].ID, keys[i].Kid, err)
|
||
continue
|
||
}
|
||
set.Keys = append(set.Keys, *k)
|
||
}
|
||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", jwksMaxAge))
|
||
auth.WriteJSON(w, http.StatusOK, set)
|
||
}
|
||
}
|