Support metadata address and scp scope validation

This commit is contained in:
Warren Chen 2026-07-07 13:27:18 +09:00
parent bb2e6c31ed
commit bb219fcbb3
5 changed files with 24 additions and 6 deletions

View File

@ -58,11 +58,11 @@ Member Center 是 OAuth2/OIDC issuer 與 JWKS 提供者。File Access Agent 不
- `files:metadata.read`
- `files:delete`
最新版 `member_center` 文件已明確把 File Access 納入同一套租戶授權模型:`file_api` client 必須綁定 `tenant_id`upload token 與 delegated download token 也都必須帶 `tenant_id`
最新版 `member_center` 文件已明確把 File Access 納入同一套租戶授權模型:`file_api` client 必須綁定 `tenant_id`upload token 與 delegated download token 也都必須帶 `tenant_id`Client Credentials 可核發的 scope 由 Member Center 的 OAuth application `usage` 對應 mapping 決定,不應依賴呼叫端用 `scope` 參數自行擴權。
File Access Agent 應驗證:
- upload 的 S2S JWT透過 Member Center JWKS 驗 `iss``aud=file_access_api``exp``scope``tenant_id`
- upload 的 S2S JWT透過 Member Center JWKS 驗 `iss``aud=file_access_api``exp``scope`/`scp``tenant_id`
- download 的 delegated token以 Member Center validation endpoint 線上驗證
- token 中的 `tenant_id` 是否符合實際 request
- token 中的 `object_key``file_id` 是否符合實際 request
@ -175,6 +175,15 @@ docker run --rm -p 8080:8080 \
file-access-agent-api
```
若 production 的 token `iss` 是公開 HTTPS issuer但 Agent 需要透過內網網址讀取 OIDC metadata/JWKS可保留 `Auth__Issuer` 為公開 issuer並另外設定 `Auth__MetadataAddress`,例如:
```bash
-e Auth__Issuer=https://member.example.com/ \
-e Auth__MetadataAddress=http://member-center-api/.well-known/openid-configuration
```
`MemberCenter__BaseUrl` 則是 Agent 呼叫 `/oauth/token` 與 delegated download token validation endpoint 的 server-to-server base URL可依部署網路使用內網位址。
若使用 local storage建議掛載 volume避免 container 移除後檔案遺失:
```bash
@ -261,4 +270,4 @@ docker run --rm -p 8080:8080 \
- 已確認使用 C# / ASP.NET Core。
- `tenant_id` 與 delegated download token 分工已依最新版 `member_center` 文件校正。
- API 已提供 Dockerfile可用 .NET 8 多階段建置產生 container image。
- 目前目錄中的 .NET skeleton 可作為正式起點,但仍需依最新流程補上 Member Center validation 串接
- API 已實作 Member Center JWT 驗證、delegated download token validation 串接,以及 local / MinIO storage adapter

View File

@ -5,7 +5,7 @@
目前狀態:
- File Access Agent API本文件定義為本專案目標契約。
- Member Center File Access API`../member_center/docs/openapi.yaml` 與相關文件整理,作為本專案整合依據。
- Member Center File Access API`../member_center/docs/openapi.yaml` 與相關文件整理,作為本專案整合依據。Client Credentials 可核發的 scope 由 Member Center 的 OAuth application `usage` mapping 決定,不依賴呼叫端自行擴權。
## 0. 部署與資料假設
@ -51,6 +51,7 @@
- 必要 scope`files:upload.write`
- 必要 claim`tenant_id`
- 驗證方式File Access Agent 以 Member Center JWKS 驗簽 JWT
- 若 token issuer 是公開 HTTPS 位址,但 JWKS 需走內網位址,可設定 `Auth:MetadataAddress` 覆寫 metadata discovery URL`iss` 仍以 `Auth:Issuer` 驗證。
### 2.2 Download
@ -116,7 +117,7 @@ Validation
- JWT signature / `iss` / `aud=file_access_api` / `exp`
- token `tenant_id`
- `scope=files:upload.write`
- `scope` 或 `scp` 包含 `files:upload.write`
- request `objectKey` 是否符合允許的 tenant 邊界策略
Object key 命名建議(介接規範):

View File

@ -43,11 +43,16 @@ builder.Services
.AddJwtBearer(options =>
{
options.Authority = authOptions.Issuer;
if (!string.IsNullOrWhiteSpace(authOptions.MetadataAddress))
{
options.MetadataAddress = authOptions.MetadataAddress;
}
options.Audience = authOptions.Audience;
options.RequireHttpsMetadata = authOptions.RequireHttpsMetadata;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = authOptions.Issuer,
ValidateAudience = true,
ValidAudience = authOptions.Audience,
ValidateLifetime = true
@ -317,6 +322,7 @@ static IResult? EnsureJwtScopeAndTenant(HttpContext context, string requiredScop
static bool HasScope(ClaimsPrincipal user, string expectedScope)
{
var scopes = user.FindAll("scope")
.Concat(user.FindAll("scp"))
.SelectMany(claim => claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
return scopes.Contains(expectedScope, StringComparer.Ordinal);
}
@ -379,6 +385,7 @@ public sealed class AuthOptions
public const string SectionName = "Auth";
public string Issuer { get; set; } = string.Empty;
public string? MetadataAddress { get; set; }
public string Audience { get; set; } = "file_access_api";
public bool RequireHttpsMetadata { get; set; } = true;
}

View File

@ -22,6 +22,7 @@
},
"Auth": {
"Issuer": "http://localhost:7850/",
"MetadataAddress": "",
"Audience": "file_access_api",
"RequireHttpsMetadata": false
},

View File

@ -119,7 +119,7 @@ public sealed class MemberCenterDelegatedDownloadTokenValidator : IDelegatedDown
}
_cachedAccessToken = token.AccessToken;
_cachedAccessTokenExpiresAt = DateTimeOffset.UtcNow.AddSeconds(Math.Max(30, token.ExpiresIn));
_cachedAccessTokenExpiresAt = DateTimeOffset.UtcNow.AddSeconds(Math.Max(0, token.ExpiresIn));
return _cachedAccessToken;
}
finally