第5讲:MCP Server 的认证与授权

发布时间:2026/11/5 15:59:33

第5讲:MCP Server 的认证与授权 一、MCP 协议的认证缺口回顾前四讲我们实现了L1工具 description 不含内部信息L2Transport 层 TLS Origin 校验 非 root 运行L3工具参数双层校验 Resource 路径遍历防护 输出脱敏但有一个根本问题没解决谁在调用这个工具目前的 Server 对所有请求一视同仁。Agent A 查自己的订单可以Agent B 查别人的订单也可以。这在生产环境是不可接受的。MCP 协议本身不定义认证机制——它把这个责任交给了 Transport 层。对于 Streamable HTTP Transport标准做法是在 HTTP 层面做认证在应用层面做授权。二、两个身份的分离Agent Identity vs User Identity生产级 MCP 系统需要区分两个身份维度身份代表什么典型标识作用域Agent Identity​哪个 Agent/服务在发起请求Service Account / Workload ID决定“能否调用这个工具”User Identity​哪个最终用户在背后操作User Token / Session决定“操作的是谁的数据”举例CodeReviewBotAgent以sa-code-reviewproj.iam.gserviceaccount.com的身份调用query_orders_by_user工具但工具参数里的user_id必须等于当前登录用户user_zhangsan不能查别人的订单Agent Identity 管“能不能调这个工具”User Identity 管“能操作哪些数据”。三、Scope 模型设计每个工具声明自己需要的 ScopeAgent 的 Token 里携带被授予的 Scope 列表Server 端做 Scope 校验。// 工具 - Scope 映射 var toolScopes map[string]string{ query_orders_by_user: orders:read, create_refund: orders:write, send_notification: notification:send, read_resource_logs: logs:read, } // Agent Token 里的 Scope 声明 type AgentClaims struct { Sub string json:sub // Agent Identity Scopes []string json:scopes // 被授予的权限 Exp int64 json:exp }Scope 命名惯例domain:action如orders:read、orders:write、logs:read。四、Go 实现JWT Bearer Scope 校验下面实现一个带认证授权的 MCP Server支持Agent 携带 JWT Bearer Token 调用工具Server 验证 Token 签名和过期时间Server 校验 Token 的 Scope 是否覆盖工具所需的 Scope工具 handler 接收 User IdentityToken 中的sub作为参数package main import ( crypto/hmac crypto/sha256 encoding/base64 encoding/json fmt log net/http strings time ) // ---- JWT 简单实现生产环境请用 golang-jwt/jwt/v5---- type Claims struct { Sub string json:sub // Agent Identity Scopes []string json:scopes // 权限列表 Exp int64 json:exp // 过期时间 Unix 时间戳 } var jwtSecret []byte(change-me-in-production) func signJWT(claims Claims) string { header : base64.RawURLEncoding.EncodeToString([]byte({alg:HS256,typ:JWT})) payloadBytes, _ : json.Marshal(claims) payload : base64.RawURLEncoding.EncodeToString(payloadBytes) sig : hmacSHA256(jwtSecret, header.payload) return header . payload . sig } func verifyJWT(tokenStr string) (*Claims, error) { parts : strings.Split(tokenStr, .) if len(parts) ! 3 { return nil, fmt.Errorf(invalid token format) } expectedSig : hmacSHA256(jwtSecret, parts[0].parts[1]) if parts[2] ! expectedSig { return nil, fmt.Errorf(invalid signature) } payloadBytes, err : base64.RawURLEncoding.DecodeString(parts[1]) if err ! nil { return nil, fmt.Errorf(invalid payload encoding) } var claims Claims if err : json.Unmarshal(payloadBytes, claims); err ! nil { return nil, fmt.Errorf(invalid payload json) } if time.Now().Unix() claims.Exp { return nil, fmt.Errorf(token expired) } return claims, nil } func hmacSHA256(key []byte, data string) string { mac : hmac.New(sha256.New, key) mac.Write([]byte(data)) return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) } // ---- MCP 消息结构 ---- type JSONRPCReq struct { JSONRPC string json:jsonrpc ID int json:id Method string json:method Params json.RawMessage json:params,omitempty } type JSONRPCResp struct { JSONRPC string json:jsonrpc ID int json:id Result interface{} json:result,omitempty Error *RPCError json:error,omitempty } type RPCError struct { Code int json:code Message string json:message } // ---- 工具定义与 Scope 映射 ---- type ToolSpec struct { Name string json:name Description string json:description InputSchema any json:inputSchema } var toolScopes map[string]string{ query_orders_by_user: orders:read, create_refund: orders:write, send_notification: notification:send, } var toolSpecs map[string]ToolSpec{ query_orders_by_user: { Name: query_orders_by_user, Description: 按用户 ID 查询订单摘要只读, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ user_id: map[string]interface{}{type: string, description: 用户 UUID}, }, required: []string{user_id}, }, }, create_refund: { Name: create_refund, Description: 发起退款申请, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ order_id: map[string]interface{}{type: string}, reason: map[string]interface{}{type: string}, }, required: []string{order_id}, }, }, send_notification: { Name: send_notification, Description: 发送通知给用户, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ user_id: map[string]interface{}{type: string}, message: map[string]interface{}{type: string}, }, required: []string{user_id, message}, }, }, } // ---- 带认证的 MCP Handler ---- type AuthMCPServer struct{} func (s *AuthMCPServer) Handle(claims *Claims, req JSONRPCReq) JSONRPCResp { switch req.Method { case initialize: return JSONRPCResp{ JSONRPC: 2.0, ID: req.ID, Result: map[string]interface{}{ protocolVersion: 2026-07-28, capabilities: map[string]interface{}{tools: map[string]interface{}{}}, }, } case tools/list: // 只返回 Agent 有权限的工具 var allowed []ToolSpec for name, spec : range toolSpecs { if hasScope(claims.Scopes, toolScopes[name]) { allowed append(allowed, spec) } } return JSONRPCResp{ JSONRPC: 2.0, ID: req.ID, Result: map[string]interface{}{tools: allowed}, } case tools/call: var params struct { Name string json:name Arguments json.RawMessage json:arguments } json.Unmarshal(req.Params, params) // 1. 检查 Agent 是否有该工具的 Scope requiredScope, exists : toolScopes[params.Name] if !exists { return JSONRPCResp{ JSONRPC: 2.0, ID: req.ID, Error: RPCError{Code: -32601, Message: 未知工具}, } } if !hasScope(claims.Scopes, requiredScope) { return JSONRPCResp{ JSONRPC: 2.0, ID: req.ID, Error: RPCError{Code: -32001, Message: fmt.Sprintf(权限不足需要 scope: %s, requiredScope)}, } } // 2. 执行工具简化实现 return JSONRPCResp{ JSONRPC: 2.0, ID: req.ID, Result: map[string]interface{}{ content: []map[string]interface{}{ {type: text, text: fmt.Sprintf(工具 %s 执行成功Agent: %s, params.Name, claims.Sub)}, }, }, } default: return JSONRPCResp{ JSONRPC: 2.0, ID: req.ID, Error: RPCError{Code: -32601, Message: 不支持的方法}, } } } func hasScope(scopes []string, required string) bool { for _, s : range scopes { if s required || s * { return true } } return false } // ---- HTTP Transport 带认证中间件 ---- func authMiddleware(next func(*Claims, JSONRPCReq) JSONRPCResp) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 从 Authorization Header 提取 Bearer Token auth : r.Header.Get(Authorization) if !strings.HasPrefix(auth, Bearer ) { http.Error(w, {jsonrpc:2.0,id:null,error:{code:-32001,message:missing authorization}}, http.StatusUnauthorized) return } tokenStr : strings.TrimPrefix(auth, Bearer ) claims, err : verifyJWT(tokenStr) if err ! nil { http.Error(w, fmt.Sprintf({jsonrpc:2.0,id:null,error:{code:-32001,message:%s}}, err.Error()), http.StatusUnauthorized) return } var req JSONRPCReq if err : json.NewDecoder(r.Body).Decode(req); err ! nil { http.Error(w, {jsonrpc:2.0,id:null,error:{code:-32700,message:parse error}}, http.StatusBadRequest) return } resp : next(claims, req) w.Header().Set(Content-Type, application/json) json.NewEncoder(w).Encode(resp) } } func main() { server : AuthMCPServer{} mux : http.NewServeMux() mux.HandleFunc(/mcp, authMiddleware(server.Handle)) // 生成测试 Token testClaims : Claims{ Sub: sa-code-reviewproj.iam.gserviceaccount.com, Scopes: []string{orders:read, notification:send}, Exp: time.Now().Add(1 * time.Hour).Unix(), } testToken : signJWT(testClaims) log.Printf(测试 Token有效期1小时:\n%s\n, testToken) log.Println(带认证的 MCP Server 启动于 :8082/mcp) log.Fatal(http.ListenAndServe(:8082, mux)) }测试# 1. 启动 Server拿到输出的测试 Token go run main.go # 2. 用有 orders:read 权限的 Token 列出工具只看到有权限的 TOKEN上面输出的 token curl -X POST localhost:8082/mcp \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:1,method:tools/list} # 3. 调用有权限的工具 curl -X POST localhost:8082/mcp \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:2,method:tools/call,params:{name:query_orders_by_user,arguments:{user_id:u_123}}} # 4. 调用无权限的工具create_refund 需要 orders:write应被拒绝 curl -X POST localhost:8082/mcp \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:3,method:tools/call,params:{name:create_refund,arguments:{order_id:o_456}}} # 5. 不带 Token 调用应被拒绝 curl -X POST localhost:8082/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:4,method:tools/list}五、安全分层L4本讲在 L1-L3 基础上补充L4认证层Transport 层强制 Bearer Token无 Token 请求返回 401Token 必须校验签名和过期时间Token 支持 DPoP 绑定防止 Token 被盗用后在其他客户端重放生产环境用 OAuth 2.0 / OIDC 颁发 Token不用自签 HMACL4授权层每个工具声明所需 ScopeServer 在tools/call时校验tools/list只返回 Agent 有权限的工具减少模型选错的概率Scope 遵循最小权限原则不给*通配符除非管理工具写操作工具orders:write额外要求人工确认第9讲展开六、延伸阅读OAuth 2.0 for MCPdraftMCP 社区正在制定的 OAuth 2.0 授权流程草案DPoPRFC 9449Demonstration of Proof-of-Possession防止 Bearer Token 被盗用golang-jwt/jwt v5Go 生产级 JWT 库支持多种签名算法和验证回调七、下一讲预告第6讲MCP Client 架构——Agent Runtime 如何与 Server 协作前面五讲全是 Server 端视角。下一讲切换到 Client 端Agent Runtime 如何发现 Server、如何缓存工具列表、如何处理错误与重试、如何管理多个 Server 的会话生命周期。我们会用 Go 实现一个轻量 MCP Client对接我们写的 Server。开发之余的小工具推荐处理 Base64、JSON 格式化、JWT 解析、Crontab 计算、PDF 合并压缩这些碎片需求我常用一个纯前端本地工具箱zz365.top。所有计算在浏览器完成文件不上服务器关页即清。免费、无登录、无广告适合开发者当常驻标签页。
延伸阅读

更多相关文章

2026/11/3 21:39:01

第7讲:通知机制与实时性——MCP 的事件驱动能力

一、为什么需要通知 前六讲的交互模式全是 Client 主动拉取(Polling): Client 调用 tools/list 获取工具列表Client 调用 resources/read 读取日志Client 调用 tools/call 执行动作 这种模式在大多数场景下够用,但有三个典型痛…

2026/11/2 12:09:32

定时任务学习

一.理论基础前置复习:什么是完全二叉树:除了最后一层外其他层都达到最大节点数,且最后一层节点都靠左排列1.小顶堆小顶堆(Min-Heap)是一种特殊的完全二叉树结构,核心原则只有一条:堆序性质:任意一个父节点的值,都小于或等于它的子节点的值。也…

2026/11/2 4:59:46

PMSM离线参数辨识:从仿真模型到最小二乘实现

简介:永磁同步电机(PMSM)离线辨识是获取定子电阻与电感等关键参数、支撑矢量控制等策略优化的基础工作。这套仿真模型面向电机控制方向的研究者与工程师,提供了从数据采集、模型搭建到参数估计与验证的完整闭环。压缩包共5个文件&…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 13:55:19

GD32E10x官方例程入门:从GPIO点灯到串口调试

简介:这份GD32E10x系列官方例程面向嵌入式硬件与单片机开发者,基于ARM Cortex-M3内核,汇集兆易创新GD32E10x的示例代码,适合工业控制、消费电子、通信设备等场景的学习、验证与二次开发。压缩包共935个文件,约11.16MB&…

2026/11/5 13:50:18

基于Django的运维平台实战:资产、发布、巡检、告警一体化

简介:这份资源是一套基于Django框架开发的运维平台源码,面向具备Python基础、希望了解Web运维系统实现思路的开发者与运维人员,可用于学习主机管理、服务监控、日志查看、任务调度、权限控制、资源统计、脚本执行与故障告警等常见运维模块的落…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑