身份感知人工智能代理系统
一个安全的多层人工智能代理系统,用户身份从前端身份验证通过代理层传播到资源API。该架构在三个独立的级别上实施访问控制,提供深度防御。
目录
______________________________________________________________________
系统设计
概述
该系统使用 OAuth 2.0与OpenID连接(OIDC) 对于身份验证和授权,支持多个身份提供商(Microsoft Entra ID和AWS Cognito):
- 协议:使用PKCE(代码交换证明密钥)的OAuth 2.0授权代码流
- 令牌格式:由IdP签名的JWT访问令牌,通过JWKS(JSON Web密钥集)验证
- 前端:使用MSAL.js 3.6(Entra)+AWS Amplify 6(Cognito)进行反应——统一的身份验证提供者
- 网关:A2A用于代理发现和请求路由的协议服务器
- 代理:谷歌ADK与克劳德·海库4.5通过LiteLLM(AWS基岩)
- 工具:提供身份识别软件工具的FastMCP服务器(Graph API+S3)
技术栈
| 组件 | 技术 | 端口 | 目的 |
|---|---|---|---|
| 前端 | React 18+MSAL.js 3.6+AWS Amplify 6 | 10003 | 用户身份验证、令牌获取 |
| 网关 | A2A协议(FastAPI+A2A-sdk) | 10000 | 代理发现、代理级ACL、流媒体 |
| 代理 | 谷歌ADK+LiteLLM+Claude Haiku 4.5(基岩) | 10001 | LLM编排、工具调用 |
| 工具 | FastMCP(无状态HTTP) | 10002 | 工具执行、令牌传播 |
| 身份 | Entra ID+AWS Cognito | - | OAuth 2.0+OIDC,多IdP JWT验证 |
| 资源 | Microsoft Graph API、AWS S3 | - | 用户数据、文件、电子邮件、云存储 |
三层安全模型
| 级别 | 位置 | 机制 | 强制执行 | 拒绝访问 |
|---|---|---|---|---|
| 1.代理人 | A2A服务器 | 组成员资格,阻止列表 | auth_middleware | 用户被阻止或不在允许的组中 |
| 2.工具 | FastMCP | 基于角色的权限(RBAC) | 内置身份验证(JWT验证)+ auth=require_role() 装饰师+ UserContextMiddleware | 令牌无效、未担任角色或角色缺乏工具权限 |
| 3.资源 | Graph API | OAuth作用域 | Microsoft Graph | 缺少所需作用域的令牌 |
______________________________________________________________________
当前状态
工作特点
| 功能 | 状态 | 注释 |
|---|---|---|
| 入境者身份验证 | ✅ 工作中 | MSAL.js弹出/静默令牌获取 |
| AWS Cognito身份验证 | ✅ 工作 | 增强托管UI重定向流 |
| 自定义API范围 | ✅ 工作 | api://{client-id}/access_as_user |
| 多IdP令牌验证 | ✅ 工作中 | Entra v1.0/v2.0+Cognito JWTs |
| A2A协议 | ✅ 工作 | 代理卡发现、消息/发送 |
| ADK+克劳德·海库4.5 | ✅ 工作 | 通过LiteLLM在AWS基岩上 |
| MCP工具(无状态) | ✅ 正在工作 | 通过header_provider传递的令牌 |
| 多回合对话 | ✅ 工作 | 会话状态保留在ADK中 |
| RBAC执行 | ✅ 工作 | 三层访问控制 |
| 海外建筑运营管理局代币交换 | ✅ 工作 | 通过On-Behalf-Of流绘制API图形 |
| S3工具 | ✅ 工作 | 服务器端AWS凭据 |
| X—承担角色 | ✅ 工作 | 端到端角色传播 |
已知限制
| 限制 | 影响 |
|---|---|
前端使用缓冲 /chat 不 /chat/stream | 一次显示所有响应 |
| 内存中的会话服务 | 重新启动时丢失 |
| JWKS缓存永远不会时间失效(A2A) | 可能会出现过期密钥(未命中时自动清除) |
| 未处理组超龄 | Entra>记录了150个组,但未提取 |
______________________________________________________________________
架构图
系统架构
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ USER BROWSER │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ React Frontend (Port 10003) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────────┐ │ │
│ │ │ MSAL.js │───▶│ Auth State │───▶│ Chat Interface │ │ │
│ │ │ (Login) │ │ (Token) │ │ - Send messages │ │ │
│ │ └──────────────┘ └──────────────┘ │ - Display responses │ │ │
│ │ │ │ │ - Scope selection │ │ │
│ │ ▼ ▼ └──────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Microsoft Entra ID (OAuth 2.0 + OIDC) │ │ │
│ │ │ - Token issuance (custom API scope + Graph scopes) │ │ │
│ │ │ - Group claims in token │ │ │
│ │ └─────────────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
│ HTTP POST + Bearer Token
▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ A2A Gateway (Port 10000) │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ auth_middleware │ │
│ │ 1. Validate JWT signature (JWKS from Entra ID) │ │
│ │ 2. Check blocklist (BLOCKED_USERS) │ │
│ │ 3. Verify group membership (ALLOWED_GROUPS) │ │
│ │ 4. Set ContextVar: current_user_claims, current_access_token │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ IdentityAwareAgentExecutor │ │
│ │ 1. Extract user claims from ContextVar │ │
│ │ 2. Ensure ADK session exists for user │ │
│ │ 3. Forward request to ADK Agent │ │
│ │ 4. Emit TaskStatusUpdateEvent with response │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
│ HTTP POST + Bearer Token
▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ ADK Agent (Port 10001) │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ Session Management │ │
│ │ - InMemorySessionService │ │
│ │ - Session state: user:access_token, user:email, user:role, user:groups │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ LlmAgent (Claude Haiku 4.5 via LiteLLM) │ │
│ │ - Processes user message │ │
│ │ - Decides which tools to call │ │
│ │ - Formats final response │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ ┌────────────────────────────────────────────────┐ │
│ │ Local Tools │ │ McpToolset │ │
│ │ - get_identity_info │ │ - header_provider injects Bearer token │ │
│ │ - check_my_permissions│ │ - Calls FastMCP server │ │
│ └────────────────────────┘ └────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
│ HTTP POST + Bearer Token (from header_provider)
▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ FastMCP Server (Port 10002) │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ FastMCP Built-in Auth (MultiAuth) │ │
│ │ 1. AzureJWTVerifier (v2.0) — auto-configured from Azure app registration │ │
│ │ 2. JWTVerifier (v1.0 fallback) — for sts.windows.net issuer tokens │ │
│ │ 3. Validates JWT signature, issuer, audience, expiry via JWKS │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ UserContextMiddleware │ │
│ │ 1. Detect IdP provider from iss claim (entra/cognito/auth0) │ │
│ │ 2. Extract email, groups from validated token claims │ │
│ │ 3. Resolve available roles via TomlPolicyEvaluator (permissions.toml) │ │
│ │ 4. Validate X-Assume-Role header against available roles │ │
│ │ 5. Set ContextVars: current_user_token, current_user_role, current_user_email│ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ Per-Tool Auth Decorators │ │
│ │ 1. auth=require_role() checks user role via ContextVar │ │
│ │ 2. Raises ToolError with [TOOL_DENIAL] prefix on denial │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ MCP Tools │ │
│ │ - get_user_profile (all roles) │ │
│ │ - list_files (admin, developer) │ │
│ │ - send_email (admin only) │ │
│ │ - delete_resource (admin only) │ │
│ │ - get_current_time (admin only) │ │
│ │ - convert_timezone (admin only) │ │
│ │ - get_time_difference (admin only) │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
│ (Optional) Graph API calls with user token
▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Microsoft Graph API │
│ - /me (User.Read scope) │
│ - /me/drive/root/children (Files.Read scope) │
│ - /me/sendMail (Mail.Send scope) │
└─────────────────────────────────────────────────────────────────────────────────────┘请求流程图
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Frontend │ │ A2A │ │ ADK │ │ MCP │
│ Browser │ │ React │ │ Gateway │ │ Agent │ │ Server │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │ │
│ 1. Login │ │ │ │
│───────────────▶│ │ │ │
│ │ 2. MSAL │ │ │
│ │ acquireToken │ │ │
│ │───────────────▶│ Entra ID │ │
│ │◀───────────────│ (Token) │ │
│ │ │ │ │
│ 3. Send msg │ │ │ │
│───────────────▶│ │ │ │
│ │ 4. POST / │ │ │
│ │ + Bearer token │ │ │
│ │───────────────▶│ │ │
│ │ │ 5. Validate │ │
│ │ │ JWT │ │
│ │ │ 6. Check │ │
│ │ │ groups │ │
│ │ │ │ │
│ │ │ 7. POST /chat │ │
│ │ │ + Bearer token │ │
│ │ │───────────────▶│ │
│ │ │ │ 8. Store token │
│ │ │ │ in session │
│ │ │ │ │
│ │ │ │ 9. LLM decides │
│ │ │ │ tool call │
│ │ │ │ │
│ │ │ │ 10. MCP call │
│ │ │ │ + Bearer token │
│ │ │ │───────────────▶│
│ │ │ │ │ 11. Validate
│ │ │ │ │ JWT
│ │ │ │ │ 12. Check
│ │ │ │ │ role
│ │ │ │ │ 13. Execute
│ │ │ │ │ tool
│ │ │ │◀───────────────│
│ │ │ │ 14. Tool result│
│ │ │ │ │
│ │ │ │ 15. LLM formats│
│ │ │ │ response │
│ │ │◀───────────────│ │
│ │ │ 16. Response │ │
│ │◀───────────────│ │ │
│ │ 17. A2A result │ │ │
│◀───────────────│ │ │ │
│ 18. Display │ │ │ │
│ │ │ │ │______________________________________________________________________
身份验证流程
系统使用 使用PKCE的OAuth 2.0授权代码流 通过Microsoft Entra ID。MSAL.js 3.6处理前端流——不使用隐式授权。令牌是包含OAuth 2.0/OIDC声明的标准JWT(iss, aud, exp, scp, groups, preferred_username等等),并在每一层使用提供商的JWKS公钥进行验证。
初始身份验证
1. User clicks "Sign In with Microsoft"
2. MSAL.js opens popup to Entra ID login (Authorization Code + PKCE)
3. User authenticates with credentials/MFA
4. Entra ID returns an authorization code to the SPA
5. MSAL.js exchanges the code for tokens (PKCE-secured, no client secret):
- ID Token (OIDC — user identity claims)
- Access Token (OAuth 2.0 — with custom API scope + Graph scopes)
6. MSAL.js caches tokens in browser storage
7. Frontend shows authenticated state请求令牌获取
// Frontend: authConfig.js
const API_SCOPE = `api://${clientId}/access_as_user`;
export const graphScopes = {
basic: [API_SCOPE, 'User.Read'],
files: [API_SCOPE, 'User.Read', 'Files.Read'],
email: [API_SCOPE, 'User.Read', 'Mail.Send'],
full: [API_SCOPE, 'User.Read', 'Files.Read', 'Mail.Send'],
destructive: [API_SCOPE, 'User.Read', 'Files.Read', 'Files.ReadWrite.All', 'Mail.Send'],
};
// Token acquisition (cached, no re-prompt unless expired)
const token = await instance.acquireTokenSilent({ scopes, account });令牌格式和验证
访问令牌是 JWT(JSON Web令牌) 由Entra ID使用RS256签名。每个后端层通过从Microsoft的JWKS端点获取公钥并验证签名、颁发者、受众和到期时间来独立验证Bearer令牌。Entra ID可以发行v1.0代币(发行人: sts.windows.net/{tenant}/)或v2.0令牌(发行者: login.microsoftonline.com/{tenant}/v2.0)--两者都支持。
A2A服务器 --手动JWT验证:
# a2a_server/server.py — manual JWKS fetching, supports v1.0 and v2.0 tokens
JWKS_URIS = [
f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys",
f"https://login.microsoftonline.com/{TENANT_ID}/discovery/keys",
"https://login.microsoftonline.com/common/discovery/keys",
]
VALID_ISSUERS = [
f"https://login.microsoftonline.com/{TENANT_ID}/v2.0",
f"https://sts.windows.net/{TENANT_ID}/",
]MCP 服务器 --FastMCP 3.1内置身份验证 MultiAuth:
# mcp_server/server.py — uses FastMCP's built-in auth providers
# v2.0 verifier (primary)
AzureJWTVerifier(client_id=CLIENT_ID, tenant_id=TENANT_ID, required_scopes=["access_as_user"])
# v1.0 verifier (fallback for sts.windows.net issuer tokens)
JWTVerifier(
jwks_uri=f"https://login.microsoftonline.com/{TENANT_ID}/discovery/keys",
issuer=f"https://sts.windows.net/{TENANT_ID}/",
audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
required_scopes=["access_as_user"],
)
# Composed via MultiAuth — tries v2.0 first, falls back to v1.0______________________________________________________________________
多回合对话流程
根据请求重新验证令牌
| 层 | 动作 | 频率 | 缓存? |
|---|---|---|---|
| 前端 | acquireTokenSilent() | 每个请求 | 是(SWITCHache) |
| A2A服务器 | JWT签名验证 | 每个请求 | JWKS缓存 |
| A2A服务器 | 组成员资格检查 | 每个请求 | 否 |
| ADK代理 | 在会话中存储令牌 | 每个请求 | 会话状态 |
| MCP 服务器 | JWT签名验证 | 每次工具调用 | JWKS缓存 |
| MCP 服务器 | 角色权限检查 | 每次工具调用 | 否 |
ADK中的会话状态
# Session state stored with user: prefix for persistence
session.state = {
"user:access_token": "", # Updated each request
"user:email": "user@domain.com",
"user:name": "User Name",
"user:groups": ["group-id-1", "group-id-2"],
"user:role": "admin" # Derived from groups
}多转弯示例
Turn 1: "What's my email?"
├─ Token validated at A2A (cached JWKS)
├─ Session created in ADK
├─ Local tool: get_identity_info
└─ Response: "Your email is user@domain.com"
Turn 2: "What time is it in Tokyo?"
├─ Token validated at A2A (same JWKS)
├─ Session retrieved (same session_id)
├─ Token updated in session state
├─ MCP tool: get_current_time
│ ├─ Token validated at MCP (cached JWKS)
│ └─ Role check: admin required ✓
└─ Response: "The time in Tokyo is..."
Turn 3: "List my files"
├─ Token validated at A2A
├─ Session retrieved
├─ MCP tool: list_files
│ ├─ Token validated at MCP
│ └─ Role check: admin/developer required ✓
└─ Response: "Your files are..."______________________________________________________________________
上下文和身份传递
A2A服务器→ ADK代理
# A2A Server uses ContextVar to pass auth data
current_user_claims: ContextVar[dict] = ContextVar("current_user_claims", default={})
current_access_token: ContextVar[str] = ContextVar("current_access_token", default="")
# Set in auth_middleware
current_user_claims.set(claims)
current_access_token.set(token)
# Read in IdentityAwareAgentExecutor
user_claims = current_user_claims.get()
access_token = current_access_token.get()
# Forward to ADK Agent via HTTP
POST /chat
{
"message": "user message",
"user_id": "sub-claim-value",
"session_id": "session-uuid"
}
Headers: Authorization: Bearer ADK代理→ MCP 服务器
# McpToolset uses header_provider for dynamic auth
def mcp_header_provider(readonly_context: ReadonlyContext) -> Dict[str, str]:
if readonly_context and readonly_context.state:
access_token = readonly_context.state.get("user:access_token", "")
if access_token:
return {"Authorization": f"Bearer {access_token}"}
return {}
# McpToolset configuration
self.mcp_toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(url=MCP_SERVER_URL),
header_provider=mcp_header_provider, # Injects token per-request
)MCP服务器上下文变量
# MCP Server stores validated claims in ContextVar (stateless mode — no ctx.get_state())
current_user_token: ContextVar[str] = ContextVar("current_user_token", default="")
current_user_role: ContextVar[str] = ContextVar("current_user_role", default="none")
current_user_email: ContextVar[str] = ContextVar("current_user_email", default="")
# Set by UserContextMiddleware, read by tools and auth= callables
@mcp.tool(auth=require_role("admin"))
async def get_current_time(timezone: str = "UTC") -> dict:
user_role = current_user_role.get()
user_email = current_user_email.get()
# ... tool logic______________________________________________________________________
RBAC(基于角色的访问控制)
角色继承
admin → Full access to all tools
developer → Access to profile and file tools
viewer → Access to profile tools only
none → No tool access (agent-level denied)组到角色映射(permissions.toml)
角色定义见 permissions.toml (代理人所有,未经授权)。复制自 permissions.example.toml:
# permissions.toml — Agent-owned role assignments
# Group-to-role mapping is the PRIMARY mechanism.
# User-level overrides are optional for exceptions.
[group_rules.entra]
"" = "admin"
"" = "developer"
"" = "viewer"
[group_rules.cognito]
# "platform-admins" = "admin" # Future: Cognito groups
[group_rules.auth0]
# "admin" = "admin" # Future: Auth0 roles
[users]
# Optional direct user overrides (case-insensitive email lookup)
# "user@company.com" = { role = "admin" }
[defaults]
unknown_users = "none"这 TomlPolicyEvaluator (mcp_server/policy.py)通过文件更改时的热重新加载,从组声明和用户覆盖中解析可用角色。角色优先级:管理员>开发人员>查看器。
角色选择:用户必须设置 X-Assume-Role MCP工具调用的标题。 UserContextMiddleware 根据可用角色验证假定的角色。对于工具列表,将自动使用优先级最高的角色。
备注:A2A服务器仍在使用GROUP_TO_ROLE来自env变量(ADMIN_GROUP_ID等等)用于代理级访问控制。MCP服务器使用permissions.toml用于工具级RBAC。
工具权限矩阵
| 工具 | 允许的角色 | 描述 |
|---|---|---|
get_user_profile | 管理员、开发人员、查看器 | 获取Microsoft Graph配置文件(或令牌声明回退) |
list_files | 管理员、开发人员 | 列出OneDrive文件 |
send_email | admin | 通过Graph发送电子邮件 |
delete_resource | admin | 删除资源 |
get_current_time | admin | 获取时区时间 |
convert_timezone | admin | 时区转换 |
get_time_difference | admin | 比较时区偏移 |
权限执行代码
# MCP Server: Per-tool auth= decorators (require_role only — IdP scope checking removed)
@mcp.tool(auth=require_role("admin", "developer", "viewer"))
async def get_user_profile() -> dict: ...
@mcp.tool(auth=require_role("admin", "developer"))
async def list_files(folder_path: str = "/") -> dict: ...
@mcp.tool(auth=require_role("admin"))
async def send_email(to: str, subject: str, body: str) -> dict: ...
# Auth helper
def require_role(*allowed_roles):
# Reads current_user_role ContextVar (set by UserContextMiddleware)
# Raises: "[TOOL_DENIAL] Access denied: Role '...' cannot use this tool. Required roles: [...]"
...拒绝访问示例
No X-Assume-Role header:
→ [ROLE_SELECTION] Role selection required. Set X-Assume-Role header to one of: ['admin']
Viewer tries to list files:
→ [TOOL_DENIAL] Access denied: Role 'viewer' cannot use this tool. Required roles: ['admin', 'developer']
Developer tries to send email:
→ [TOOL_DENIAL] Access denied: Role 'developer' cannot use this tool. Required roles: ['admin']
User tries to assume a role they don't have:
→ [TOOL_DENIAL] Cannot assume role 'admin'. Available roles: ['developer']
No-group user (not in permissions.toml):
→ [TOOL_DENIAL] No roles available for user@domain.com. Contact admin to assign group membership.
User without groups at A2A gateway:
→ 403 {"error": "access_denied", "denial_level": "agent", "denial_reason": "no_group_membership"}
Blocked user:
→ 403 {"error": "access_denied", "denial_level": "agent", "denial_reason": "blocked_user"}______________________________________________________________________
API 参考
A2A服务器(端口10000)
代理卡发现
GET /.well-known/agent-card.json答复:
{
"name": "Identity-Aware AI Agent",
"description": "An AI agent with Entra ID authentication...",
"url": "http://localhost:10000/",
"version": "1.0.0",
"capabilities": {"streaming": true},
"skills": [...]
}发送消息(A2A协议)
POST /
Authorization: Bearer
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"messageId": "msg-123",
"role": "user",
"parts": [{"type": "text", "text": "What time is it?"}]
}
},
"id": "req-123"
}答复:
{
"id": "req-123",
"jsonrpc": "2.0",
"result": {
"id": "task-uuid",
"kind": "task",
"status": {
"state": "completed",
"message": {
"kind": "message",
"role": "agent",
"parts": [{"kind": "text", "text": "The current time is..."}]
}
}
}
}健康检查
GET /health安全上下文
GET /me
Authorization: Bearer 答复:
{
"user": { "email": "user@domain.com", "name": "User Name", "oid": "..." },
"security": {
"role": "developer",
"groups": ["group-id-1"],
"group_names": { "group-id-1": "developer" },
"token_scopes": ["User.Read", "Files.Read"],
"token_expiry": 1741363200,
"issuer": "https://login.microsoftonline.com/{tenant}/v2.0"
},
"permissions": {
"get_user_profile": true, "list_files": true, "send_email": false,
"delete_resource": false, "get_current_time": false,
"convert_timezone": false, "get_time_difference": false
},
"tool_scopes": {
"get_user_profile": ["User.Read"], "list_files": ["Files.Read"],
"send_email": ["Mail.Send"], "delete_resource": ["Files.ReadWrite.All"],
"get_current_time": [], "convert_timezone": [], "get_time_difference": []
}
}ADK代理(端口10001)
创建会话
POST /session
Authorization: Bearer
Content-Type: application/json
{
"user_id": "user-sub-claim",
"user_info": {
"email": "user@domain.com",
"name": "User Name",
"groups": ["group-id-1"]
}
}答复:
{"session_id": "session-uuid"}聊天
POST /chat
Authorization: Bearer
Content-Type: application/json
{
"message": "What's my email?",
"user_id": "user-sub-claim",
"session_id": "session-uuid"
}答复:
{
"response": "Your email is user@domain.com",
"session_id": "session-uuid"
}MCP服务器(端口10002)
MCP端点
POST /mcp
Authorization: Bearer
Content-Type: application/json
# Standard MCP protocol messages可用工具
| 工具 | 参数 | 返回 |
|---|---|---|
get_user_profile | (无) | 来自Graph或令牌声明的用户配置文件 |
list_files | folder_path?: string | 文件名列表 |
send_email | to, subject, body | 发送状态 |
delete_resource | resource_id | 删除状态 |
get_current_time | timezone?: string | 带纪元的时间信息 |
convert_timezone | time_str, from_timezone, to_timezone | 转换时间 |
get_time_difference | timezone1, timezone2 | 偏移差 |
______________________________________________________________________
安装指南
先决条件
- Python 3.10+
- Node.js 18+
- Microsoft Entra ID租户
- Anthropic API密钥(用于Claude)
1.微软Entra ID配置
- 在中创建应用程序注册 Entra管理中心
- 配置平台:单页应用
- 重定向URI: http://localhost:10003, http://localhost:10003/redirect
- 公开API:
- 添加范围: access_as_user - 应用程序ID URI: api://{client-id}
- API权限:
- openid, profile, User.Read, Files.Read, Mail.Send
- 令牌配置:
- 添加组声明(安全组)
- 创建安全组:
- 管理员组、开发人员组、查看器组
2.环境设置
# Backend (.env)
cp .env.example .env
# Required variables:
ENTRA_CLIENT_ID=
ENTRA_TENANT_ID=
ADMIN_GROUP_ID=
DEVELOPER_GROUP_ID=
VIEWER_GROUP_ID=
ANTHROPIC_API_KEY=
# Frontend (frontend/.env)
cd frontend && cp .env.example .env
REACT_APP_ENTRA_CLIENT_ID=
REACT_APP_ENTRA_TENANT_ID=
# MCP Permissions (group-to-role mapping)
cp permissions.example.toml permissions.toml
# Edit permissions.toml with your Entra ID security group GUIDs3.安装依赖项
# Backend
pip install -r requirements.txt
# or with uv:
uv pip install -r requirements.txt
# Frontend
cd frontend && npm install4.运行服务
# Terminal 1: MCP Server
uv run python mcp_server/server.py
# Terminal 2: ADK Agent
uv run python adk_agent/agent.py
# Terminal 3: A2A Gateway
uv run python a2a_server/server.py
# Terminal 4: Frontend
cd frontend && npm start5.访问申请
- 前端:http://localhost:10003
- A2A网关:http://localhost:10000
- 代理卡:http://localhost:10000/.well-已知/代理卡.json
______________________________________________________________________
安全测试仪表板
前端包括一个全面的安全测试仪表板,用于测试和可视化三层访问控制系统。
仪表板组件
| 组件 | 位置 | 目的 |
|---|---|---|
| 安全上下文面板 | 左侧边栏 | 显示角色、组、范围、令牌到期倒计时 |
| 令牌检查器 | 左侧边栏(可折叠) | 解码JWT标头/有效载荷以供显示 |
| RBAC测试矩阵 | 左侧边栏 | 一键式测试网格,带通过/失败跟踪 |
| 对话选项卡 | 主区域 | 多选项卡聊天,每个选项卡范围选择(最多4个) |
| 审核日志 | 主区域(底部) | 拒绝分类的请求历史记录 |
| 拒绝指示器 | 内联徽章 | 颜色编码的拒绝层:代理(红色)、工具(橙色)、资源(紫色) |
| 帐户切换器 | 标题 | 支持多帐户以测试不同角色 |
拒绝分类
拒绝被自动分为四个等级:
| 层 | 检测者 | 示例 |
|---|---|---|
| 代理 | http401/403+ denial_level 在响应正文中 | 用户不在任何组中 |
| 工具 | [TOOL_DENIAL] 响应文本中的标记 | 查看器正在尝试 list_files |
| RESOURCE | 响应文本中的Graph API 403 | Graph权限不足 |
前端文件结构
frontend/src/
App.js # Dashboard layout shell
App.css # Dark theme styles
authConfig.js # MSAL config + scope presets (basic, files, email, full, destructive)
index.js # MSAL provider setup
components/
AuthStatus.js # Multi-account switcher dropdown
LoginPrompt.js # Sign-in prompt
SecurityContextPanel.js # Role, groups, scopes, expiry countdown
TokenInspector.js # JWT decoder display
ConversationTabs.js # Multi-tab chat with per-tab scope
ChatInterface.js # Chat with denial tagging and latency tracking
RBACTestMatrix.js # One-click test grid
AuditLog.js # Request history table
DenialIndicator.js # Color-coded denial badge
utils/
tokenDecoder.js # JWT base64url decode helper
denialClassifier.js # HTTP status + response text → denial tier
testScenarios.js # Predefined test prompts with expected outcomes______________________________________________________________________
测试
运行测试
# With services running
uv run pytest tests/test_access_control.py -v手动测试
- 使用管理员组中的用户登录
- 测试:“我的电子邮件是什么?”→ 应该可以
- 测试:“东京现在几点了?”→ 应该工作(仅限管理员)
- 测试:“列出我的文件”→ 应该可以
- 使用查看器组中的用户登录
- 测试:“我的电子邮件是什么?”→ 应该可以
- 测试:“东京现在几点了?”→ 应该失败(仅限管理员)
检查日志
# All logs in logs/ directory
tail -f logs/a2a_server.log
tail -f logs/adk_agent.log
tail -f logs/mcp_server.log______________________________________________________________________
文档链接
______________________________________________________________________
许可证
麻省理工学院
