refactor: 使用标准库实现 OIDC 客户端流程
This commit is contained in:
@@ -2,6 +2,8 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -12,24 +14,31 @@ import (
|
||||
)
|
||||
|
||||
func TestOIDCPublicClientUsesAuthorizationCodePKCES256WithoutSecret(t *testing.T) {
|
||||
const pkceVerifier = "test-pkce-verifier-with-at-least-43-characters-1234"
|
||||
var issuer string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/.well-known/openid-configuration":
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"issuer": issuer, "authorization_endpoint": issuer + "/authorize",
|
||||
"token_endpoint": issuer + "/token", "revocation_endpoint": issuer + "/revoke",
|
||||
"token_endpoint": issuer + "/token", "jwks_uri": issuer + "/jwks",
|
||||
"revocation_endpoint": issuer + "/revoke",
|
||||
"end_session_endpoint": issuer + "/logout",
|
||||
})
|
||||
case "/token":
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
values, _ := url.ParseQuery(string(body))
|
||||
if values.Get("client_secret") != "" || strings.Contains(r.Header.Get("Authorization"), "Basic") {
|
||||
t.Fatal("public client token request must not contain client credentials")
|
||||
t.Error("public client token request must not contain client credentials")
|
||||
http.Error(w, "invalid client authentication", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if values.Get("grant_type") != "authorization_code" || values.Get("client_id") != "gateway-public" || values.Get("code_verifier") != "verifier" {
|
||||
t.Fatalf("unexpected token request: %v", values)
|
||||
if values.Get("grant_type") != "authorization_code" || values.Get("client_id") != "gateway-public" || values.Get("code_verifier") != pkceVerifier {
|
||||
t.Error("token request omitted authorization code, public client id, or PKCE verifier")
|
||||
http.Error(w, "invalid token request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"access_token": "access-token", "refresh_token": "refresh-token",
|
||||
"id_token": "id-token", "expires_in": 300, "token_type": "Bearer",
|
||||
@@ -48,16 +57,18 @@ func TestOIDCPublicClientUsesAuthorizationCodePKCES256WithoutSecret(t *testing.T
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
authorizationURL, err := client.AuthorizationURL(context.Background(), "state", "nonce", "challenge")
|
||||
authorizationURL, err := client.AuthorizationURL(context.Background(), "state", "nonce", pkceVerifier)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parsed, _ := url.Parse(authorizationURL)
|
||||
query := parsed.Query()
|
||||
if query.Get("response_type") != "code" || query.Get("code_challenge_method") != "S256" || query.Get("code_challenge") != "challenge" {
|
||||
digest := sha256.Sum256([]byte(pkceVerifier))
|
||||
expectedChallenge := base64.RawURLEncoding.EncodeToString(digest[:])
|
||||
if query.Get("response_type") != "code" || query.Get("code_challenge_method") != "S256" || query.Get("code_challenge") != expectedChallenge {
|
||||
t.Fatalf("authorization request is not PKCE S256: %v", query)
|
||||
}
|
||||
if _, err := client.ExchangeCode(context.Background(), "authorization-code", "verifier"); err != nil {
|
||||
if _, err := client.ExchangeCode(context.Background(), "authorization-code", pkceVerifier); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -82,7 +93,8 @@ func TestOIDCPublicClientRefreshAndRevokeNeverSendSecret(t *testing.T) {
|
||||
case "/.well-known/openid-configuration":
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"issuer": issuer, "authorization_endpoint": issuer + "/authorize",
|
||||
"token_endpoint": issuer + "/token", "revocation_endpoint": issuer + "/revoke",
|
||||
"token_endpoint": issuer + "/token", "jwks_uri": issuer + "/jwks",
|
||||
"revocation_endpoint": issuer + "/revoke",
|
||||
"end_session_endpoint": issuer + "/logout",
|
||||
})
|
||||
case "/token", "/revoke":
|
||||
@@ -90,12 +102,17 @@ func TestOIDCPublicClientRefreshAndRevokeNeverSendSecret(t *testing.T) {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
values, _ := url.ParseQuery(string(body))
|
||||
if values.Get("client_secret") != "" || r.Header.Get("Authorization") != "" {
|
||||
t.Fatal("public client request contained client authentication")
|
||||
t.Error("public client request contained client authentication")
|
||||
http.Error(w, "invalid client authentication", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/token" {
|
||||
if values.Get("grant_type") != "refresh_token" || values.Get("refresh_token") != "old-refresh" {
|
||||
t.Fatalf("unexpected refresh request: %v", values)
|
||||
t.Error("refresh request omitted grant type or refresh token")
|
||||
http.Error(w, "invalid refresh request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"access_token": "new-access", "refresh_token": "new-refresh", "expires_in": 300})
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user