第二十二章:WIMSE — 多系统环境中的工作负载身份

“当工作负载跨越多个系统边界时,身份不应该在途中丢失。”

        mindmap
  root((WIMSE))
    背景
      IETF 工作组
      跨系统身份
      与 SPIFFE 互补
    核心概念
      WIT
      Txn-Token
      Token Exchange
    应用场景
      多云
      SaaS-to-SaaS
      混合云
    与现有标准
      OAuth 2.0
      SPIFFE
      OIDC
    

22.1 WIMSE 是什么

WIMSE(Workload Identity in Multi-System Environments)是 IETF 正在制定的标准,解决跨系统环境中工作负载身份传递的问题。

WIMSE 架构总览

        flowchart TB
    subgraph UserLayer["用户层"]
        User["👤 终端用户"]
    end

    subgraph ControlPlane["控制平面"]
        IdP["身份提供者<br/>(IdP / OIDC)"]
        TES["Token Exchange<br/>Service"]
        TTS["Txn-Token<br/>Service"]
        PolicyEngine["策略引擎<br/>(OPA / Cedar)"]
    end

    subgraph DataPlane["数据平面 — 多系统环境"]
        subgraph AWS["AWS"]
            SA["Service A<br/>IAM Role"]
        end
        subgraph GCP["GCP"]
            SB["Service B<br/>Service Account"]
        end
        subgraph Azure["Azure"]
            SC["Service C<br/>Managed Identity"]
        end
    end

    subgraph IdentityLayer["身份基础设施"]
        SPIFFE["SPIFFE / SPIRE"]
        CertAuth["证书颁发机构<br/>(CA)"]
    end

    User -->|"认证"| IdP
    IdP -->|"用户身份"| TTS
    SPIFFE -->|"SVID 签发"| SA & SB & SC
    CertAuth -->|"X.509 证书"| SPIFFE

    SA -->|"WIT + Txn-Token"| TES
    TES -->|"交换后的 Token"| SB
    SB -->|"WIT + Txn-Token"| SC

    TTS -->|"签发 Txn-Token"| SA
    PolicyEngine -->|"授权决策"| SB & SC

    style UserLayer fill:#E3F2FD,stroke:#1565C0
    style ControlPlane fill:#FFF3E0,stroke:#E65100
    style DataPlane fill:#E8F5E9,stroke:#2E7D32
    style IdentityLayer fill:#F3E5F5,stroke:#6A1B9A
    

为什么需要 WIMSE

SPIFFE 解决了”工作负载如何获得身份”的问题,但在复杂的多系统调用链中,还有未解决的问题:

        flowchart LR
    User["👤 用户"] --> SA["Service A<br/>(AWS IAM Role)"]
    SA --> SB["Service B<br/>(GCP Service Account)"]
    SB --> SC["Service C<br/>(Azure Managed Identity)"]
    SC --> DB[("Database")]

    SA -.-|"❓ 如何证明<br/>代表 Service A 调用"| SB
    SB -.-|"❓ 如何传递<br/>原始用户身份"| SC
    SC -.-|"❓ 如何防止<br/>Confused Deputy"| DB

    style SA fill:#FF9800,color:#fff
    style SB fill:#4CAF50,color:#fff
    style SC fill:#2196F3,color:#fff
    

核心问题:

  1. Service C 如何知道这个请求最初来自哪个用户?

  2. Service B 如何向 Service C 证明”我是代表 Service A 调用的”?

  3. 如何防止 Confused Deputy 攻击?

22.2 核心概念

Workload Identity Token(WIT)

WIT 是基于 JWT 的工作负载身份令牌,标识工作负载自身的身份:

{
  "iss": "https://issuer.example.com",
  "sub": "spiffe://example.com/service-a",
  "aud": "spiffe://example.com/service-b",
  "exp": 1709510400,
  "iat": 1709506800,
  "jti": "unique-token-id",
  "cnf": {
    "kid": "key-binding-id"
  }
}

Transaction Token(Txn-Token)

Txn-Token 在调用链中传递,记录完整的调用上下文:

{
  "iss": "https://txn-token-service.example.com",
  "iat": 1709506800,
  "aud": "https://api.example.com",
  "txn": "txn-abc-123",
  "sub": {
    "format": "oidc_id_token",
    "iss": "https://idp.example.com",
    "sub": "user-alice"
  },
  "rctx": {
    "req_ip": "192.168.1.100",
    "req_method": "POST",
    "req_path": "/api/transfer"
  },
  "purp": "transfer_funds"
}

Workload Identity Token 交换流程

        sequenceDiagram
    autonumber
    participant User as 👤 用户
    participant SA as Service A<br/>(AWS)
    participant TTS as Txn-Token<br/>Service
    participant TES as Token Exchange<br/>Service
    participant SB as Service B<br/>(GCP)
    participant SC as Service C<br/>(Azure)

    User->>SA: 请求 + 用户凭证 (OIDC ID Token)
    SA->>SA: 获取本地 WIT (SPIFFE SVID)

    SA->>TTS: 请求 Txn-Token<br/>(用户身份 + 请求上下文)
    TTS-->>SA: 签发 Txn-Token<br/>(包含用户身份、调用目的)

    SA->>TES: Token Exchange 请求<br/>(AWS WIT → GCP Token)
    TES->>TES: 验证 AWS WIT<br/>查找信任映射
    TES-->>SA: 返回 GCP 可识别的 Token

    SA->>SB: 请求 + 交换后的 Token + Txn-Token
    SB->>SB: 验证 Token 和 Txn-Token<br/>检查调用链完整性

    SB->>TES: Token Exchange 请求<br/>(GCP WIT → Azure Token)
    TES-->>SB: 返回 Azure 可识别的 Token

    SB->>SC: 请求 + 交换后的 Token + Txn-Token
    SC->>SC: 验证 Token 和 Txn-Token<br/>确认原始用户身份
    SC-->>SB: 响应
    SB-->>SA: 响应
    SA-->>User: 响应
    

多系统环境信任链

        flowchart TB
    subgraph TrustRoot["全局信任根"]
        RootCA["Root CA / Trust Anchor"]
    end

    subgraph TrustDomainAWS["信任域: aws.example.com"]
        SPIRE_AWS["SPIRE Server<br/>(AWS)"]
        WIT_A["WIT: spiffe://aws.example.com/service-a"]
        WIT_A2["WIT: spiffe://aws.example.com/service-d"]
    end

    subgraph TrustDomainGCP["信任域: gcp.example.com"]
        SPIRE_GCP["SPIRE Server<br/>(GCP)"]
        WIT_B["WIT: spiffe://gcp.example.com/service-b"]
    end

    subgraph TrustDomainAzure["信任域: azure.example.com"]
        SPIRE_Azure["SPIRE Server<br/>(Azure)"]
        WIT_C["WIT: spiffe://azure.example.com/service-c"]
    end

    subgraph Federation["跨域信任联邦"]
        TES2["Token Exchange Service"]
        BundleEndpoint["Trust Bundle<br/>Endpoint"]
    end

    RootCA -->|"签发中间 CA"| SPIRE_AWS & SPIRE_GCP & SPIRE_Azure
    SPIRE_AWS -->|"签发 SVID"| WIT_A & WIT_A2
    SPIRE_GCP -->|"签发 SVID"| WIT_B
    SPIRE_Azure -->|"签发 SVID"| WIT_C

    SPIRE_AWS <-->|"Trust Bundle 交换"| BundleEndpoint
    SPIRE_GCP <-->|"Trust Bundle 交换"| BundleEndpoint
    SPIRE_Azure <-->|"Trust Bundle 交换"| BundleEndpoint

    WIT_A -->|"Token Exchange"| TES2
    TES2 -->|"跨域 Token"| WIT_B
    WIT_B -->|"Token Exchange"| TES2
    TES2 -->|"跨域 Token"| WIT_C

    style TrustRoot fill:#FFCDD2,stroke:#B71C1C
    style TrustDomainAWS fill:#FF9800,color:#000
    style TrustDomainGCP fill:#4CAF50,color:#000
    style TrustDomainAzure fill:#2196F3,color:#000
    style Federation fill:#FFF9C4,stroke:#F57F17
    

Token Exchange(RFC 8693)

Token Exchange 允许工作负载将一种 Token 交换为另一种:

        flowchart LR
    subgraph Source["源系统 (AWS)"]
        SA2["Service A"]
        AWSToken["AWS IAM Token"]
    end

    subgraph Exchange["Token Exchange Service"]
        Validate["① 验证源 Token"]
        Map["② 身份映射"]
        Issue["③ 签发目标 Token"]
    end

    subgraph Target["目标系统 (GCP)"]
        SB2["Service B"]
        GCPToken["GCP SA Token"]
    end

    SA2 --> AWSToken
    AWSToken -->|"subject_token"| Validate
    Validate --> Map
    Map --> Issue
    Issue -->|"access_token"| GCPToken
    GCPToken --> SB2

    style Source fill:#FF9800,color:#000
    style Exchange fill:#FFF9C4,stroke:#F57F17
    style Target fill:#4CAF50,color:#000
    
import httpx

async def exchange_token(
    subject_token: str,
    subject_token_type: str,
    target_audience: str,
) -> str:
    """OAuth 2.0 Token Exchange (RFC 8693)"""
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://token-exchange.example.com/token",
            data={
                "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
                "subject_token": subject_token,
                "subject_token_type": subject_token_type,
                "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
                "audience": target_audience,
            }
        )
        return response.json()["access_token"]

# 将 SPIFFE JWT-SVID 交换为目标系统的 Token
new_token = await exchange_token(
    subject_token=spiffe_jwt_svid,
    subject_token_type="urn:ietf:params:oauth:token-type:jwt",
    target_audience="spiffe://gcp.example.com/service-b",
)

22.3 Confused Deputy 攻击

        sequenceDiagram
    participant Eve as 🦹 攻击者 Eve
    participant SA3 as Service A<br/>(被迷惑)
    participant SB3 as Service B

    Note over Eve,SB3: ❌ 无 Txn-Token 保护 — Confused Deputy 攻击
    Eve->>SA3: 伪造请求
    SA3->>SB3: 用 Service A 自身权限调用<br/>(不携带调用者身份)
    SB3-->>SA3: ✅ 执行成功(不应该!)
    SA3-->>Eve: 返回结果

    Note over Eve,SB3: ✅ 有 Txn-Token 保护 — 攻击被阻止
    Eve->>SA3: 伪造请求
    SA3->>SA3: 创建 Txn-Token<br/>记录 Eve 的身份
    SA3->>SB3: 请求 + Txn-Token<br/>(包含 Eve 的身份)
    SB3->>SB3: 检查 Txn-Token<br/>发现请求者是 Eve
    SB3-->>SA3: ❌ 拒绝!Eve 无权限
    SA3-->>Eve: 请求被拒绝
    

22.4 WIMSE 与现有标准的关系

标准

解决的问题

WIMSE 的关系

SPIFFE

工作负载如何获得身份

互补:WIMSE 传递 SPIFFE 身份

OAuth 2.0

委托授权

扩展:Token Exchange

OIDC

用户身份认证

集成:Txn-Token 携带用户身份

mTLS

传输层身份验证

补充:应用层身份传递

22.5 应用场景

多云环境

        flowchart LR
    AWSsvc["AWS 服务<br/>spiffe://aws.../a"] -->|"WIT"| TES3["Token Exchange"]
    TES3 -->|"WIT (已转换)"| GCPsvc["GCP 服务<br/>spiffe://gcp.../b"]

    style AWSsvc fill:#FF9800,color:#fff
    style TES3 fill:#FFF9C4,stroke:#F57F17
    style GCPsvc fill:#4CAF50,color:#fff
    

SaaS-to-SaaS

        flowchart LR
    SaaSA["SaaS A<br/>用户在 A 中触发操作"] -->|"Txn-Token<br/>(携带用户身份)"| SaaSB["SaaS B<br/>知道请求来自 A 的用户<br/>可以做细粒度授权"]

    style SaaSA fill:#7E57C2,color:#fff
    style SaaSB fill:#26A69A,color:#fff
    

22.6 WIMSE vs SPIFFE 对比

维度

WIMSE

SPIFFE

定位

跨系统身份传递上下文保持

工作负载身份签发验证

标准组织

IETF 工作组(草案阶段)

CNCF 毕业项目

核心产物

WIT、Txn-Token

SVID(X.509 / JWT)

身份格式

JWT(兼容 SPIFFE URI)

SPIFFE ID(URI 格式)

信任建立

依赖底层身份系统(可以是 SPIFFE)

自建 Trust Domain + CA

调用链追踪

✅ Txn-Token 原生支持

❌ 不涉及(需额外方案)

用户身份传递

✅ Txn-Token 携带用户上下文

❌ 仅关注工作负载身份

Token 交换

✅ 基于 RFC 8693

❌ 不涉及(需 Federation)

跨云支持

✅ 设计目标之一

⚠️ 需要 Federation 配置

成熟度

草案阶段,概念验证

生产就绪,广泛采用

实现

尚无官方参考实现

SPIRE(官方参考实现)

关系

消费 SPIFFE 身份,增加传递层

提供底层身份,被 WIMSE 使用

总结: SPIFFE 回答”我是谁”,WIMSE 回答”我代表谁、为什么调用、经过了哪些系统”。两者是互补关系,WIMSE 建立在 SPIFFE 等身份系统之上。

22.7 多云环境工作负载身份方案对比

维度

WIMSE + SPIFFE

AWS IAM
Roles Anywhere

GCP Workload
Identity Federation

Azure Workload
Identity Federation

HashiCorp Vault

跨云支持

✅ 原生跨云

⚠️ 仅 AWS 入站

⚠️ 仅 GCP 入站

⚠️ 仅 Azure 入站

✅ 多云

标准化

IETF + CNCF

AWS 专有

Google 专有

Microsoft 专有

HashiCorp 专有

调用链追踪

✅ Txn-Token

用户上下文

✅ 原生支持

⚠️ 有限

⚠️ 有限

⚠️ 有限

无密钥

✅ SVID 自动轮转

✅ X.509 证书

✅ OIDC 联邦

✅ OIDC 联邦

⚠️ 需要初始认证

身份格式

SPIFFE ID (URI)

IAM Role ARN

Service Account

Managed Identity

Vault Token/Cert

部署复杂度

🔴 高(需 SPIRE + TES)

🟡 中

🟢 低

🟢 低

🟡 中

供应商锁定

🟢 无

🔴 AWS

🔴 GCP

🔴 Azure

🟡 低

成熟度

🟡 草案/早期

🟢 GA

🟢 GA

🟢 GA

🟢 GA

社区生态

🟡 成长中

🟢 丰富

🟢 丰富

🟢 丰富

🟢 丰富

选型建议:

  • 单云环境:优先使用云厂商原生方案(IAM Roles Anywhere / Workload Identity Federation)

  • 多云 + 简单场景:HashiCorp Vault 作为统一身份中介

  • 多云 + 复杂调用链:WIMSE + SPIFFE 提供最完整的跨系统身份传递能力(待标准成熟)

22.8 实现示例

Go 实现

WIMSE Token 创建和验证(使用 go-jose)

package wimse

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"errors"
	"time"

	"github.com/go-jose/go-jose/v4"
	"github.com/go-jose/go-jose/v4/jwt"
)

// WIT (Workload Identity Token) 的 Claims
type WITClaims struct {
	jwt.Claims
	// cnf (confirmation) 用于 Proof-of-Possession
	Confirmation *Confirmation `json:"cnf,omitempty"`
}

type Confirmation struct {
	KeyID string `json:"kid,omitempty"`
	JWK   *jose.JSONWebKey `json:"jwk,omitempty"`
}

// TxnTokenClaims 表示 Transaction Token 的 Claims
type TxnTokenClaims struct {
	jwt.Claims
	TransactionID string            `json:"txn"`
	Subject       *TxnSubject       `json:"sub_id,omitempty"`
	RequestContext map[string]string `json:"rctx,omitempty"`
	Purpose       string            `json:"purp,omitempty"`
}

type TxnSubject struct {
	Format  string `json:"format"`
	Issuer  string `json:"iss,omitempty"`
	Subject string `json:"sub,omitempty"`
}

// WITIssuer 负责签发 Workload Identity Token
type WITIssuer struct {
	issuer     string
	signingKey *ecdsa.PrivateKey
	keyID      string
	signer     jose.Signer
}

// NewWITIssuer 创建一个新的 WIT 签发器
func NewWITIssuer(issuer string) (*WITIssuer, error) {
	// 生成 ECDSA P-256 密钥对
	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}

	keyID := "wit-key-" + time.Now().Format("20060102150405")

	signerOpts := jose.SignerOptions{}
	signerOpts.WithType("wimse-id+jwt")
	signerOpts.WithHeader("kid", keyID)

	signer, err := jose.NewSigner(
		jose.SigningKey{Algorithm: jose.ES256, Key: privateKey},
		&signerOpts,
	)
	if err != nil {
		return nil, err
	}

	return &WITIssuer{
		issuer:     issuer,
		signingKey: privateKey,
		keyID:      keyID,
		signer:     signer,
	}, nil
}

// IssueWIT 签发一个 Workload Identity Token
func (w *WITIssuer) IssueWIT(subject, audience string, ttl time.Duration) (string, error) {
	now := time.Now()

	claims := WITClaims{
		Claims: jwt.Claims{
			Issuer:    w.issuer,
			Subject:   subject,
			Audience:  jwt.Audience{audience},
			IssuedAt:  jwt.NewNumericDate(now),
			Expiry:    jwt.NewNumericDate(now.Add(ttl)),
			NotBefore: jwt.NewNumericDate(now),
			ID:        generateJTI(),
		},
		Confirmation: &Confirmation{
			KeyID: w.keyID,
		},
	}

	token, err := jwt.Signed(w.signer).Claims(claims).Serialize()
	if err != nil {
		return "", err
	}

	return token, nil
}

// IssueTxnToken 签发一个 Transaction Token
func (w *WITIssuer) IssueTxnToken(
	txnID string,
	userIssuer string,
	userSubject string,
	audience string,
	reqCtx map[string]string,
	purpose string,
) (string, error) {
	now := time.Now()

	claims := TxnTokenClaims{
		Claims: jwt.Claims{
			Issuer:   w.issuer,
			Audience: jwt.Audience{audience},
			IssuedAt: jwt.NewNumericDate(now),
			Expiry:   jwt.NewNumericDate(now.Add(30 * time.Second)),
		},
		TransactionID: txnID,
		Subject: &TxnSubject{
			Format:  "oidc_id_token",
			Issuer:  userIssuer,
			Subject: userSubject,
		},
		RequestContext: reqCtx,
		Purpose:        purpose,
	}

	token, err := jwt.Signed(w.signer).Claims(claims).Serialize()
	if err != nil {
		return "", err
	}

	return token, nil
}

// WITVerifier 负责验证 Workload Identity Token
type WITVerifier struct {
	trustedIssuers map[string]*ecdsa.PublicKey
}

// NewWITVerifier 创建一个新的 WIT 验证器
func NewWITVerifier() *WITVerifier {
	return &WITVerifier{
		trustedIssuers: make(map[string]*ecdsa.PublicKey),
	}
}

// AddTrustedIssuer 添加一个受信任的签发者
func (v *WITVerifier) AddTrustedIssuer(issuer string, publicKey *ecdsa.PublicKey) {
	v.trustedIssuers[issuer] = publicKey
}

// VerifyWIT 验证一个 Workload Identity Token
func (v *WITVerifier) VerifyWIT(tokenString string, expectedAudience string) (*WITClaims, error) {
	token, err := jwt.ParseSigned(tokenString, []jose.SignatureAlgorithm{jose.ES256})
	if err != nil {
		return nil, err
	}

	// 先解析未验证的 claims 以获取 issuer
	var unverified WITClaims
	if err := token.UnsafeClaimsWithoutVerification(&unverified); err != nil {
		return nil, err
	}

	// 查找受信任的签发者公钥
	publicKey, ok := v.trustedIssuers[unverified.Issuer]
	if !ok {
		return nil, errors.New("untrusted issuer: " + unverified.Issuer)
	}

	// 使用公钥验证签名并解析 claims
	var claims WITClaims
	if err := token.Claims(publicKey, &claims); err != nil {
		return nil, err
	}

	// 验证标准 claims
	expected := jwt.Expected{
		Audience: jwt.Audience{expectedAudience},
		Time:     time.Now(),
	}
	if err := claims.Claims.Validate(expected); err != nil {
		return nil, err
	}

	return &claims, nil
}

func generateJTI() string {
	b := make([]byte, 16)
	rand.Read(b)
	return fmt.Sprintf("%x", b)
}

Workload Identity Federation 客户端

package wimse

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"
)

// TokenExchangeResponse 表示 RFC 8693 Token Exchange 的响应
type TokenExchangeResponse struct {
	AccessToken     string `json:"access_token"`
	IssuedTokenType string `json:"issued_token_type"`
	TokenType       string `json:"token_type"`
	ExpiresIn       int    `json:"expires_in"`
	Scope           string `json:"scope,omitempty"`
}

// FederationClient 实现跨系统的 Workload Identity Federation
type FederationClient struct {
	tokenEndpoint string
	httpClient    *http.Client

	mu          sync.RWMutex
	cachedToken *TokenExchangeResponse
	expiresAt   time.Time
}

// NewFederationClient 创建一个新的 Federation 客户端
func NewFederationClient(tokenEndpoint string) *FederationClient {
	return &FederationClient{
		tokenEndpoint: tokenEndpoint,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// ExchangeToken 执行 OAuth 2.0 Token Exchange (RFC 8693)
func (fc *FederationClient) ExchangeToken(
	ctx context.Context,
	subjectToken string,
	subjectTokenType string,
	targetAudience string,
) (*TokenExchangeResponse, error) {
	// 检查缓存
	fc.mu.RLock()
	if fc.cachedToken != nil && time.Now().Before(fc.expiresAt) {
		token := fc.cachedToken
		fc.mu.RUnlock()
		return token, nil
	}
	fc.mu.RUnlock()

	// 构建 Token Exchange 请求
	data := url.Values{
		"grant_type":           {"urn:ietf:params:oauth:grant-type:token-exchange"},
		"subject_token":        {subjectToken},
		"subject_token_type":   {subjectTokenType},
		"requested_token_type": {"urn:ietf:params:oauth:token-type:access_token"},
		"audience":             {targetAudience},
	}

	req, err := http.NewRequestWithContext(
		ctx, "POST", fc.tokenEndpoint,
		strings.NewReader(data.Encode()),
	)
	if err != nil {
		return nil, fmt.Errorf("creating request: %w", err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := fc.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("executing token exchange: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("token exchange failed (status %d): %s",
			resp.StatusCode, string(body))
	}

	var tokenResp TokenExchangeResponse
	if err := json.Unmarshal(body, &tokenResp); err != nil {
		return nil, fmt.Errorf("parsing response: %w", err)
	}

	// 缓存 Token(提前 30 秒过期以留出余量)
	fc.mu.Lock()
	fc.cachedToken = &tokenResp
	fc.expiresAt = time.Now().Add(
		time.Duration(tokenResp.ExpiresIn-30) * time.Second,
	)
	fc.mu.Unlock()

	return &tokenResp, nil
}

// CrossSystemCall 执行跨系统调用,自动处理 Token 交换和 Txn-Token 传递
func (fc *FederationClient) CrossSystemCall(
	ctx context.Context,
	targetURL string,
	localWIT string,
	txnToken string,
	targetAudience string,
) (*http.Response, error) {
	// 1. 将本地 WIT 交换为目标系统可识别的 Token
	exchanged, err := fc.ExchangeToken(
		ctx,
		localWIT,
		"urn:ietf:params:oauth:token-type:jwt",
		targetAudience,
	)
	if err != nil {
		return nil, fmt.Errorf("token exchange: %w", err)
	}

	// 2. 构建请求,携带交换后的 Token 和 Txn-Token
	req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil)
	if err != nil {
		return nil, err
	}

	// 交换后的 Token 用于身份认证
	req.Header.Set("Authorization", "Bearer "+exchanged.AccessToken)
	// Txn-Token 用于传递调用链上下文
	req.Header.Set("Txn-Token", txnToken)

	return fc.httpClient.Do(req)
}

Python 实现

WIMSE Token 解析器(使用 PyJWT)

"""WIMSE Token 解析器 — 基于 PyJWT 实现 WIT 和 Txn-Token 的创建与验证"""

from __future__ import annotations

import uuid
import time
from dataclasses import dataclass, field
from typing import Any

import jwt
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend


@dataclass
class WITClaims:
    """Workload Identity Token Claims"""
    issuer: str
    subject: str          # 通常是 SPIFFE ID
    audience: str
    issued_at: float
    expires_at: float
    jti: str = field(default_factory=lambda: str(uuid.uuid4()))
    confirmation: dict | None = None


@dataclass
class TxnTokenClaims:
    """Transaction Token Claims"""
    issuer: str
    audience: str
    transaction_id: str
    subject_format: str   # e.g., "oidc_id_token"
    subject_issuer: str
    subject_id: str
    request_context: dict[str, str] = field(default_factory=dict)
    purpose: str = ""


class WIMSETokenIssuer:
    """签发 WIMSE Token(WIT 和 Txn-Token)"""

    def __init__(self, issuer: str):
        self.issuer = issuer
        # 生成 ECDSA P-256 密钥对
        self._private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
        self._public_key = self._private_key.public_key()
        self._key_id = f"wit-key-{uuid.uuid4().hex[:8]}"

    @property
    def public_key(self):
        return self._public_key

    @property
    def key_id(self) -> str:
        return self._key_id

    def issue_wit(
        self,
        subject: str,
        audience: str,
        ttl_seconds: int = 300,
    ) -> str:
        """签发 Workload Identity Token"""
        now = time.time()
        payload = {
            "iss": self.issuer,
            "sub": subject,
            "aud": audience,
            "iat": int(now),
            "nbf": int(now),
            "exp": int(now + ttl_seconds),
            "jti": str(uuid.uuid4()),
            "cnf": {"kid": self._key_id},
        }
        return jwt.encode(
            payload,
            self._private_key,
            algorithm="ES256",
            headers={"typ": "wimse-id+jwt", "kid": self._key_id},
        )

    def issue_txn_token(
        self,
        transaction_id: str,
        user_issuer: str,
        user_subject: str,
        audience: str,
        request_context: dict[str, str] | None = None,
        purpose: str = "",
        ttl_seconds: int = 30,
    ) -> str:
        """签发 Transaction Token"""
        now = time.time()
        payload = {
            "iss": self.issuer,
            "aud": audience,
            "iat": int(now),
            "exp": int(now + ttl_seconds),
            "txn": transaction_id,
            "sub_id": {
                "format": "oidc_id_token",
                "iss": user_issuer,
                "sub": user_subject,
            },
            "rctx": request_context or {},
            "purp": purpose,
        }
        return jwt.encode(
            payload,
            self._private_key,
            algorithm="ES256",
            headers={"typ": "wimse-txn+jwt", "kid": self._key_id},
        )


class WIMSETokenVerifier:
    """验证 WIMSE Token"""

    def __init__(self):
        self._trusted_issuers: dict[str, Any] = {}

    def add_trusted_issuer(self, issuer: str, public_key) -> None:
        """注册受信任的签发者公钥"""
        self._trusted_issuers[issuer] = public_key

    def verify_wit(self, token: str, expected_audience: str) -> dict:
        """验证 Workload Identity Token"""
        # 先解码 header 获取 kid,再解码未验证的 payload 获取 issuer
        unverified = jwt.decode(token, options={"verify_signature": False})
        issuer = unverified.get("iss")

        public_key = self._trusted_issuers.get(issuer)
        if public_key is None:
            raise ValueError(f"Untrusted issuer: {issuer}")

        claims = jwt.decode(
            token,
            public_key,
            algorithms=["ES256"],
            audience=expected_audience,
            options={"require": ["iss", "sub", "aud", "exp", "iat", "jti"]},
        )
        return claims

    def verify_txn_token(self, token: str, expected_audience: str) -> dict:
        """验证 Transaction Token"""
        unverified = jwt.decode(token, options={"verify_signature": False})
        issuer = unverified.get("iss")

        public_key = self._trusted_issuers.get(issuer)
        if public_key is None:
            raise ValueError(f"Untrusted issuer: {issuer}")

        claims = jwt.decode(
            token,
            public_key,
            algorithms=["ES256"],
            audience=expected_audience,
            options={"require": ["iss", "aud", "exp", "txn", "sub_id"]},
        )
        return claims


# ── 使用示例 ──────────────────────────────────────────────
if __name__ == "__main__":
    # 签发者
    issuer = WIMSETokenIssuer("https://issuer.example.com")

    # 签发 WIT
    wit = issuer.issue_wit(
        subject="spiffe://example.com/service-a",
        audience="spiffe://example.com/service-b",
    )
    print(f"WIT: {wit[:60]}...")

    # 签发 Txn-Token
    txn = issuer.issue_txn_token(
        transaction_id="txn-abc-123",
        user_issuer="https://idp.example.com",
        user_subject="user-alice",
        audience="https://api.example.com",
        request_context={"req_method": "POST", "req_path": "/api/transfer"},
        purpose="transfer_funds",
    )
    print(f"Txn-Token: {txn[:60]}...")

    # 验证者
    verifier = WIMSETokenVerifier()
    verifier.add_trusted_issuer("https://issuer.example.com", issuer.public_key)

    claims = verifier.verify_wit(wit, "spiffe://example.com/service-b")
    print(f"Verified WIT subject: {claims['sub']}")

    txn_claims = verifier.verify_txn_token(txn, "https://api.example.com")
    print(f"Verified Txn-Token txn_id: {txn_claims['txn']}")

跨系统身份传播中间件(FastAPI)

"""WIMSE 跨系统身份传播中间件 — FastAPI 实现"""

from __future__ import annotations

import uuid
import logging
from dataclasses import dataclass
from typing import Any, Callable

import httpx
import jwt
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from starlette.middleware.base import BaseHTTPMiddleware

logger = logging.getLogger("wimse")

# ── 数据模型 ──────────────────────────────────────────────

@dataclass
class WorkloadIdentity:
    """当前工作负载的身份信息"""
    spiffe_id: str
    issuer: str
    wit_token: str  # 本地 WIT


@dataclass
class CallerContext:
    """从 WIT + Txn-Token 中提取的调用者上下文"""
    workload_id: str          # 调用方工作负载 SPIFFE ID
    transaction_id: str | None
    original_user: str | None  # 原始用户身份
    user_issuer: str | None
    purpose: str | None
    request_context: dict[str, str]


# ── Token 验证 ────────────────────────────────────────────

class WIMSETokenValidator:
    """验证入站请求中的 WIMSE Token"""

    def __init__(self, trusted_jwks: dict[str, Any]):
        """
        trusted_jwks: issuer -> public_key 的映射
        """
        self._trusted_keys = trusted_jwks

    def validate_wit(self, token: str, expected_audience: str) -> dict:
        unverified = jwt.decode(token, options={"verify_signature": False})
        issuer = unverified.get("iss", "")
        key = self._trusted_keys.get(issuer)
        if key is None:
            raise ValueError(f"Untrusted WIT issuer: {issuer}")
        return jwt.decode(
            token, key, algorithms=["ES256"], audience=expected_audience,
        )

    def validate_txn_token(self, token: str, expected_audience: str) -> dict:
        unverified = jwt.decode(token, options={"verify_signature": False})
        issuer = unverified.get("iss", "")
        key = self._trusted_keys.get(issuer)
        if key is None:
            raise ValueError(f"Untrusted Txn-Token issuer: {issuer}")
        return jwt.decode(
            token, key, algorithms=["ES256"], audience=expected_audience,
        )


# ── FastAPI 中间件 ────────────────────────────────────────

class WIMSEMiddleware(BaseHTTPMiddleware):
    """
    WIMSE 身份传播中间件:
    1. 从入站请求中提取并验证 WIT 和 Txn-Token
    2. 将调用者上下文注入 request.state
    3. 为出站请求自动附加 Token
    """

    def __init__(self, app, validator: WIMSETokenValidator, local_audience: str):
        super().__init__(app)
        self.validator = validator
        self.local_audience = local_audience

    async def dispatch(self, request: Request, call_next):
        caller_ctx = self._extract_caller_context(request)
        request.state.caller_context = caller_ctx

        if caller_ctx and caller_ctx.transaction_id:
            logger.info(
                "WIMSE request: txn=%s caller=%s user=%s purpose=%s",
                caller_ctx.transaction_id,
                caller_ctx.workload_id,
                caller_ctx.original_user,
                caller_ctx.purpose,
            )

        response = await call_next(request)
        return response

    def _extract_caller_context(self, request: Request) -> CallerContext | None:
        auth_header = request.headers.get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            return None

        wit_token = auth_header[7:]
        txn_token_str = request.headers.get("Txn-Token")

        try:
            wit_claims = self.validator.validate_wit(wit_token, self.local_audience)
        except Exception as e:
            logger.warning("WIT validation failed: %s", e)
            return None

        txn_claims = None
        if txn_token_str:
            try:
                txn_claims = self.validator.validate_txn_token(
                    txn_token_str, self.local_audience,
                )
            except Exception as e:
                logger.warning("Txn-Token validation failed: %s", e)

        return CallerContext(
            workload_id=wit_claims.get("sub", ""),
            transaction_id=txn_claims.get("txn") if txn_claims else None,
            original_user=(
                txn_claims.get("sub_id", {}).get("sub") if txn_claims else None
            ),
            user_issuer=(
                txn_claims.get("sub_id", {}).get("iss") if txn_claims else None
            ),
            purpose=txn_claims.get("purp") if txn_claims else None,
            request_context=txn_claims.get("rctx", {}) if txn_claims else {},
        )


# ── 出站请求:身份传播 HTTP 客户端 ───────────────────────

class WIMSEPropagatingClient:
    """自动传播 WIMSE Token 的 HTTP 客户端"""

    def __init__(
        self,
        local_identity: WorkloadIdentity,
        token_exchange_url: str,
    ):
        self.local_identity = local_identity
        self.token_exchange_url = token_exchange_url
        self._client = httpx.AsyncClient(timeout=10.0)

    async def call(
        self,
        method: str,
        url: str,
        target_audience: str,
        txn_token: str | None = None,
        **kwargs,
    ) -> httpx.Response:
        """发起跨系统调用,自动处理 Token 交换和传播"""
        # 1. Token Exchange: 本地 WIT → 目标系统 Token
        exchanged = await self._exchange_token(target_audience)

        # 2. 构建请求头
        headers = kwargs.pop("headers", {})
        headers["Authorization"] = f"Bearer {exchanged}"
        if txn_token:
            headers["Txn-Token"] = txn_token

        return await self._client.request(method, url, headers=headers, **kwargs)

    async def _exchange_token(self, target_audience: str) -> str:
        resp = await self._client.post(
            self.token_exchange_url,
            data={
                "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
                "subject_token": self.local_identity.wit_token,
                "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
                "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
                "audience": target_audience,
            },
        )
        resp.raise_for_status()
        return resp.json()["access_token"]

    async def close(self):
        await self._client.aclose()


# ── FastAPI 应用示例 ──────────────────────────────────────

def get_caller_context(request: Request) -> CallerContext | None:
    """FastAPI 依赖项:获取 WIMSE 调用者上下文"""
    return getattr(request.state, "caller_context", None)


def require_caller_context(request: Request) -> CallerContext:
    """FastAPI 依赖项:要求必须有 WIMSE 调用者上下文"""
    ctx = getattr(request.state, "caller_context", None)
    if ctx is None:
        raise HTTPException(status_code=401, detail="Missing WIMSE identity")
    return ctx


# 示例应用
app = FastAPI(title="WIMSE-Protected Service")

# 注册中间件(实际使用时需要提供真实的 trusted_jwks)
# validator = WIMSETokenValidator(trusted_jwks={...})
# app.add_middleware(WIMSEMiddleware, validator=validator, local_audience="spiffe://example.com/service-b")


@app.get("/api/data")
async def get_data(caller: CallerContext = Depends(require_caller_context)):
    """受 WIMSE 保护的 API 端点"""
    # 可以基于调用者上下文做细粒度授权
    if caller.purpose != "read_data":
        raise HTTPException(status_code=403, detail="Purpose not allowed")

    return {
        "data": "sensitive-info",
        "caller_workload": caller.workload_id,
        "original_user": caller.original_user,
        "transaction_id": caller.transaction_id,
    }


@app.get("/api/chain")
async def chain_call(caller: CallerContext = Depends(require_caller_context)):
    """演示调用链传播:接收请求后继续调用下游服务"""
    # 此处演示如何将 Txn-Token 继续传播到下游
    # propagating_client = WIMSEPropagatingClient(...)
    # response = await propagating_client.call(
    #     "GET",
    #     "https://service-c.example.com/api/resource",
    #     target_audience="spiffe://example.com/service-c",
    #     txn_token=request.headers.get("Txn-Token"),
    # )
    return {
        "message": "Chain call would propagate Txn-Token to downstream",
        "transaction_id": caller.transaction_id,
    }

Java 实现

WIMSE Token 处理(使用 Nimbus JOSE+JWT)

package com.example.wimse;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;
import com.nimbusds.jwt.*;

import java.time.Instant;
import java.util.*;

/**
 * WIMSE Token 处理器 — 基于 Nimbus JOSE+JWT
 * 支持 WIT (Workload Identity Token) 和 Txn-Token 的签发与验证
 */
public class WIMSETokenProcessor {

    private final String issuer;
    private final ECKey signingKey;
    private final JWSSigner signer;
    private final String keyId;

    public WIMSETokenProcessor(String issuer) throws JOSEException {
        this.issuer = issuer;
        this.keyId = "wit-key-" + UUID.randomUUID().toString().substring(0, 8);

        // 生成 ECDSA P-256 密钥对
        this.signingKey = new ECKeyGenerator(Curve.P_256)
                .keyID(keyId)
                .generate();
        this.signer = new ECDSASigner(signingKey);
    }

    /**
     * 获取公钥(用于分发给验证方)
     */
    public ECKey getPublicKey() {
        return signingKey.toPublicJWK();
    }

    // ── WIT 签发 ─────────────────────────────────────────

    /**
     * 签发 Workload Identity Token
     *
     * @param subject  工作负载 SPIFFE ID,如 "spiffe://example.com/service-a"
     * @param audience 目标工作负载 SPIFFE ID
     * @param ttlSeconds Token 有效期(秒)
     * @return 签名后的 JWT 字符串
     */
    public String issueWIT(String subject, String audience, long ttlSeconds)
            throws JOSEException {
        Instant now = Instant.now();

        JWTClaimsSet claims = new JWTClaimsSet.Builder()
                .issuer(issuer)
                .subject(subject)
                .audience(audience)
                .issueTime(Date.from(now))
                .notBeforeTime(Date.from(now))
                .expirationTime(Date.from(now.plusSeconds(ttlSeconds)))
                .jwtID(UUID.randomUUID().toString())
                .claim("cnf", Map.of("kid", keyId))
                .build();

        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
                .type(new JOSEObjectType("wimse-id+jwt"))
                .keyID(keyId)
                .build();

        SignedJWT signedJWT = new SignedJWT(header, claims);
        signedJWT.sign(signer);
        return signedJWT.serialize();
    }

    // ── Txn-Token 签发 ───────────────────────────────────

    /**
     * 签发 Transaction Token
     */
    public String issueTxnToken(
            String transactionId,
            String userIssuer,
            String userSubject,
            String audience,
            Map<String, String> requestContext,
            String purpose
    ) throws JOSEException {
        Instant now = Instant.now();

        Map<String, Object> subjectId = Map.of(
                "format", "oidc_id_token",
                "iss", userIssuer,
                "sub", userSubject
        );

        JWTClaimsSet claims = new JWTClaimsSet.Builder()
                .issuer(issuer)
                .audience(audience)
                .issueTime(Date.from(now))
                .expirationTime(Date.from(now.plusSeconds(30)))
                .claim("txn", transactionId)
                .claim("sub_id", subjectId)
                .claim("rctx", requestContext)
                .claim("purp", purpose)
                .build();

        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
                .type(new JOSEObjectType("wimse-txn+jwt"))
                .keyID(keyId)
                .build();

        SignedJWT signedJWT = new SignedJWT(header, claims);
        signedJWT.sign(signer);
        return signedJWT.serialize();
    }

    // ── Token 验证 ───────────────────────────────────────

    /**
     * WIMSE Token 验证器(静态内部类)
     */
    public static class Verifier {

        private final Map<String, ECKey> trustedIssuers = new HashMap<>();

        public void addTrustedIssuer(String issuer, ECKey publicKey) {
            trustedIssuers.put(issuer, publicKey);
        }

        /**
         * 验证 Workload Identity Token
         */
        public JWTClaimsSet verifyWIT(String tokenString, String expectedAudience)
                throws Exception {
            SignedJWT signedJWT = SignedJWT.parse(tokenString);
            JWTClaimsSet claims = signedJWT.getJWTClaimsSet();

            // 查找受信任的签发者
            String iss = claims.getIssuer();
            ECKey publicKey = trustedIssuers.get(iss);
            if (publicKey == null) {
                throw new SecurityException("Untrusted issuer: " + iss);
            }

            // 验证签名
            JWSVerifier verifier = new ECDSAVerifier(publicKey);
            if (!signedJWT.verify(verifier)) {
                throw new SecurityException("Invalid WIT signature");
            }

            // 验证 audience
            if (!claims.getAudience().contains(expectedAudience)) {
                throw new SecurityException("Audience mismatch");
            }

            // 验证过期时间
            if (claims.getExpirationTime().before(new Date())) {
                throw new SecurityException("WIT has expired");
            }

            return claims;
        }

        /**
         * 验证 Transaction Token
         */
        public JWTClaimsSet verifyTxnToken(String tokenString, String expectedAudience)
                throws Exception {
            SignedJWT signedJWT = SignedJWT.parse(tokenString);
            JWTClaimsSet claims = signedJWT.getJWTClaimsSet();

            String iss = claims.getIssuer();
            ECKey publicKey = trustedIssuers.get(iss);
            if (publicKey == null) {
                throw new SecurityException("Untrusted Txn-Token issuer: " + iss);
            }

            JWSVerifier verifier = new ECDSAVerifier(publicKey);
            if (!signedJWT.verify(verifier)) {
                throw new SecurityException("Invalid Txn-Token signature");
            }

            if (!claims.getAudience().contains(expectedAudience)) {
                throw new SecurityException("Audience mismatch");
            }

            if (claims.getExpirationTime().before(new Date())) {
                throw new SecurityException("Txn-Token has expired");
            }

            // 验证必需字段
            if (claims.getStringClaim("txn") == null) {
                throw new SecurityException("Missing txn claim");
            }
            if (claims.getClaim("sub_id") == null) {
                throw new SecurityException("Missing sub_id claim");
            }

            return claims;
        }
    }

    // ── 使用示例 ─────────────────────────────────────────

    public static void main(String[] args) throws Exception {
        // 创建签发器
        WIMSETokenProcessor processor =
                new WIMSETokenProcessor("https://issuer.example.com");

        // 签发 WIT
        String wit = processor.issueWIT(
                "spiffe://example.com/service-a",
                "spiffe://example.com/service-b",
                300
        );
        System.out.println("WIT: " + wit.substring(0, 60) + "...");

        // 签发 Txn-Token
        String txnToken = processor.issueTxnToken(
                "txn-abc-123",
                "https://idp.example.com",
                "user-alice",
                "https://api.example.com",
                Map.of("req_method", "POST", "req_path", "/api/transfer"),
                "transfer_funds"
        );
        System.out.println("Txn-Token: " + txnToken.substring(0, 60) + "...");

        // 验证
        Verifier verifier = new Verifier();
        verifier.addTrustedIssuer("https://issuer.example.com", processor.getPublicKey());

        JWTClaimsSet witClaims = verifier.verifyWIT(
                wit, "spiffe://example.com/service-b");
        System.out.println("Verified WIT subject: " + witClaims.getSubject());

        JWTClaimsSet txnClaims = verifier.verifyTxnToken(
                txnToken, "https://api.example.com");
        System.out.println("Verified Txn-Token txn: " + txnClaims.getStringClaim("txn"));
    }
}

Spring Boot Workload Identity 集成

package com.example.wimse.spring;

import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.*;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.*;

/**
 * Spring Boot WIMSE Workload Identity 集成
 */
@SpringBootApplication
public class WIMSEApplication {
    public static void main(String[] args) {
        SpringApplication.run(WIMSEApplication.class, args);
    }
}

// ── 认证 Token 模型 ──────────────────────────────────────

/**
 * WIMSE 认证 Token — 封装 WIT 和 Txn-Token 的验证结果
 */
class WIMSEAuthenticationToken extends AbstractAuthenticationToken {

    private final String workloadId;       // 调用方 SPIFFE ID
    private final String transactionId;    // 事务 ID
    private final String originalUser;     // 原始用户
    private final String purpose;          // 调用目的
    private final Map<String, String> requestContext;

    public WIMSEAuthenticationToken(
            String workloadId,
            String transactionId,
            String originalUser,
            String purpose,
            Map<String, String> requestContext,
            Collection<? extends GrantedAuthority> authorities
    ) {
        super(authorities);
        this.workloadId = workloadId;
        this.transactionId = transactionId;
        this.originalUser = originalUser;
        this.purpose = purpose;
        this.requestContext = requestContext != null ? requestContext : Map.of();
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() { return null; }

    @Override
    public Object getPrincipal() { return workloadId; }

    public String getWorkloadId() { return workloadId; }
    public String getTransactionId() { return transactionId; }
    public String getOriginalUser() { return originalUser; }
    public String getPurpose() { return purpose; }
    public Map<String, String> getRequestContext() { return requestContext; }
}

// ── WIMSE 认证过滤器 ─────────────────────────────────────

/**
 * Spring Security 过滤器:验证入站请求中的 WIT 和 Txn-Token
 */
@Component
class WIMSEAuthenticationFilter extends OncePerRequestFilter {

    @Value("${wimse.local-audience:}")
    private String localAudience;

    // 实际应用中应从 JWKS 端点或配置加载
    private final Map<String, ECKey> trustedIssuers = new HashMap<>();

    public void addTrustedIssuer(String issuer, ECKey publicKey) {
        trustedIssuers.put(issuer, publicKey);
    }

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

        String authHeader = request.getHeader("Authorization");
        String txnTokenHeader = request.getHeader("Txn-Token");

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String witToken = authHeader.substring(7);
            try {
                WIMSEAuthenticationToken auth = validateAndBuild(witToken, txnTokenHeader);
                SecurityContextHolder.getContext().setAuthentication(auth);
            } catch (Exception e) {
                logger.warn("WIMSE authentication failed: " + e.getMessage());
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write(
                        "{\"error\":\"invalid_token\",\"detail\":\"" + e.getMessage() + "\"}"
                );
                return;
            }
        }

        filterChain.doFilter(request, response);
    }

    private WIMSEAuthenticationToken validateAndBuild(
            String witToken, String txnTokenStr
    ) throws Exception {
        // 验证 WIT
        SignedJWT witJWT = SignedJWT.parse(witToken);
        JWTClaimsSet witClaims = witJWT.getJWTClaimsSet();

        ECKey publicKey = trustedIssuers.get(witClaims.getIssuer());
        if (publicKey == null) {
            throw new SecurityException("Untrusted issuer: " + witClaims.getIssuer());
        }
        if (!witJWT.verify(new ECDSAVerifier(publicKey))) {
            throw new SecurityException("Invalid WIT signature");
        }
        if (witClaims.getExpirationTime().before(new Date())) {
            throw new SecurityException("WIT expired");
        }

        String workloadId = witClaims.getSubject();
        String transactionId = null;
        String originalUser = null;
        String purpose = null;
        Map<String, String> rctx = Map.of();

        // 验证 Txn-Token(如果存在)
        if (txnTokenStr != null && !txnTokenStr.isBlank()) {
            SignedJWT txnJWT = SignedJWT.parse(txnTokenStr);
            JWTClaimsSet txnClaims = txnJWT.getJWTClaimsSet();

            ECKey txnKey = trustedIssuers.get(txnClaims.getIssuer());
            if (txnKey != null && txnJWT.verify(new ECDSAVerifier(txnKey))) {
                transactionId = txnClaims.getStringClaim("txn");
                purpose = txnClaims.getStringClaim("purp");

                @SuppressWarnings("unchecked")
                Map<String, Object> subId =
                        (Map<String, Object>) txnClaims.getClaim("sub_id");
                if (subId != null) {
                    originalUser = (String) subId.get("sub");
                }

                @SuppressWarnings("unchecked")
                Map<String, String> ctx =
                        (Map<String, String>) txnClaims.getClaim("rctx");
                if (ctx != null) {
                    rctx = ctx;
                }
            }
        }

        // 根据工作负载 ID 分配权限
        List<GrantedAuthority> authorities = resolveAuthorities(workloadId, purpose);

        return new WIMSEAuthenticationToken(
                workloadId, transactionId, originalUser, purpose, rctx, authorities
        );
    }

    private List<GrantedAuthority> resolveAuthorities(String workloadId, String purpose) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_WORKLOAD"));

        // 基于 SPIFFE ID 和目的分配权限(示例策略)
        if (workloadId.contains("/service-a")) {
            authorities.add(new SimpleGrantedAuthority("ROLE_SERVICE_A"));
        }
        if ("read_data".equals(purpose)) {
            authorities.add(new SimpleGrantedAuthority("SCOPE_READ"));
        }
        if ("transfer_funds".equals(purpose)) {
            authorities.add(new SimpleGrantedAuthority("SCOPE_WRITE"));
        }
        return authorities;
    }
}

// ── Security 配置 ────────────────────────────────────────

@Configuration
class WIMSESecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(
            HttpSecurity http,
            WIMSEAuthenticationFilter wimseFilter
    ) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .addFilterBefore(wimseFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/health", "/ready").permitAll()
                .requestMatchers("/api/**").hasRole("WORKLOAD")
                .anyRequest().denyAll()
            );
        return http.build();
    }
}

// ── 出站请求:Token 交换 + 身份传播 ─────────────────────

/**
 * WIMSE 身份传播 RestTemplate 拦截器
 * 自动为出站请求执行 Token Exchange 并附加 Txn-Token
 */
@Component
class WIMSEPropagatingRestTemplate {

    @Value("${wimse.token-exchange-url:}")
    private String tokenExchangeUrl;

    @Value("${wimse.local-wit:}")
    private String localWIT;

    private final RestTemplate restTemplate = new RestTemplate();

    /**
     * 执行跨系统调用,自动处理 Token 交换和 Txn-Token 传播
     */
    public <T> ResponseEntity<T> callWithIdentity(
            String url,
            HttpMethod method,
            String targetAudience,
            String txnToken,
            Class<T> responseType
    ) {
        // 1. Token Exchange
        String exchangedToken = exchangeToken(localWIT, targetAudience);

        // 2. 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(exchangedToken);
        if (txnToken != null) {
            headers.set("Txn-Token", txnToken);
        }

        HttpEntity<Void> entity = new HttpEntity<>(headers);
        return restTemplate.exchange(url, method, entity, responseType);
    }

    private String exchangeToken(String subjectToken, String targetAudience) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        String body = String.join("&",
                "grant_type=urn:ietf:params:oauth:grant-type:token-exchange",
                "subject_token=" + subjectToken,
                "subject_token_type=urn:ietf:params:oauth:token-type:jwt",
                "requested_token_type=urn:ietf:params:oauth:token-type:access_token",
                "audience=" + targetAudience
        );

        HttpEntity<String> request = new HttpEntity<>(body, headers);
        ResponseEntity<Map> response = restTemplate.exchange(
                tokenExchangeUrl, HttpMethod.POST, request, Map.class
        );

        return (String) Objects.requireNonNull(response.getBody()).get("access_token");
    }
}

// ── 示例 Controller ──────────────────────────────────────

@RestController
@RequestMapping("/api")
class WIMSEExampleController {

    /**
     * 受 WIMSE 保护的 API 端点
     */
    @GetMapping("/data")
    public Map<String, Object> getData() {
        WIMSEAuthenticationToken auth = (WIMSEAuthenticationToken)
                SecurityContextHolder.getContext().getAuthentication();

        return Map.of(
                "data", "sensitive-info",
                "caller_workload", auth.getWorkloadId(),
                "original_user", auth.getOriginalUser() != null
                        ? auth.getOriginalUser() : "unknown",
                "transaction_id", auth.getTransactionId() != null
                        ? auth.getTransactionId() : "none",
                "purpose", auth.getPurpose() != null
                        ? auth.getPurpose() : "unspecified"
        );
    }

    /**
     * 演示 Confused Deputy 防护
     */
    @PostMapping("/transfer")
    public Map<String, Object> transfer(@RequestBody Map<String, Object> body) {
        WIMSEAuthenticationToken auth = (WIMSEAuthenticationToken)
                SecurityContextHolder.getContext().getAuthentication();

        // 检查 Txn-Token 中的目的是否匹配
        if (!"transfer_funds".equals(auth.getPurpose())) {
            return Map.of(
                    "error", "forbidden",
                    "detail", "Purpose mismatch: expected transfer_funds, got "
                            + auth.getPurpose()
            );
        }

        // 检查原始用户是否有权限
        String user = auth.getOriginalUser();
        if (user == null) {
            return Map.of("error", "forbidden", "detail", "No user context in Txn-Token");
        }

        return Map.of(
                "status", "success",
                "authorized_by", "WIMSE Txn-Token",
                "original_user", user,
                "caller_workload", auth.getWorkloadId(),
                "transaction_id", auth.getTransactionId()
        );
    }
}

22.9 小结

  • WIMSE 解决跨系统环境中的工作负载身份传递问题

  • WIT 标识工作负载自身身份,Txn-Token 传递调用链上下文

  • Token Exchange 实现跨系统的 Token 转换

  • WIMSE 与 SPIFFE 互补:SPIFFE 提供身份,WIMSE 传递身份

  • Confused Deputy 攻击是 WIMSE 要解决的核心安全问题

  • WIMSE 标准仍在 IETF 制定中,但核心概念已经清晰

  • 在多云环境中,WIMSE + SPIFFE 提供了最完整的跨系统身份传递方案,但需要根据实际成熟度和复杂度权衡选型