Support metadata address and scp scope validation
This commit is contained in:
parent
bb2e6c31ed
commit
bb219fcbb3
15
README.md
15
README.md
@ -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。
|
||||
|
||||
@ -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 命名建議(介接規範):
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
},
|
||||
"Auth": {
|
||||
"Issuer": "http://localhost:7850/",
|
||||
"MetadataAddress": "",
|
||||
"Audience": "file_access_api",
|
||||
"RequireHttpsMetadata": false
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user