欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > 【MCP Node.js SDK 全栈进阶指南】中级篇(2):MCP身份验证与授权实践

【MCP Node.js SDK 全栈进阶指南】中级篇(2):MCP身份验证与授权实践

2025/4/25 10:27:47 来源:https://blog.csdn.net/npl2580/article/details/147423103  浏览:    关键词:【MCP Node.js SDK 全栈进阶指南】中级篇(2):MCP身份验证与授权实践

前言

在MCP TypeScript-SDK初级篇中,我们已经掌握了MCP的基础知识,包括开发环境搭建、基础服务器开发、资源开发、工具开发、提示模板开发以及传输层配置等内容。随着我们构建的MCP应用变得越来越复杂,安全问题变得尤为重要。在实际生产环境中,保护敏感资源不被未授权访问是确保系统安全的关键。

本文作为中级篇的第二篇,将深入探讨MCP身份验证与授权实践,包括OAuth集成基础、权限模型设计、访问控制实现以及令牌管理与安全最佳实践。通过学习这些内容,你将能够为MCP应用构建完善的安全机制,保护你的资源和API不被滥用,同时为用户提供便捷的身份验证体验。

一、OAuth集成基础

OAuth 2.0是现代Web应用程序中最广泛使用的授权框架,MCP提供了强大的OAuth集成支持,使应用能够安全地访问用户资源而无需直接处理用户凭证。

1.1 OAuth 2.0概述

在深入MCP的OAuth集成之前,让我们先简要回顾OAuth 2.0的基本概念:

  • 资源所有者(Resource Owner): 通常是用户,拥有受保护资源的权限
  • 客户端(Client): 请求访问资源的应用程序
  • 授权服务器(Authorization Server): 验证资源所有者身份并颁发访问令牌
  • 资源服务器(Resource Server): 托管受保护资源的服务器,接受并验证访问令牌

OAuth 2.0定义了几种授权流程,包括:

  1. 授权码(Authorization Code)流程: 适用于具有后端的Web应用
  2. 隐式(Implicit)流程: 适用于单页应用(SPA)
  3. 资源所有者密码凭证(Resource Owner Password Credentials)流程: 适用于可信应用
  4. 客户端凭证(Client Credentials)流程: 适用于服务器间通信

MCP TypeScript-SDK支持这些标准OAuth流程,同时提供了简化的API使旇集成更加容易。

1.2 在MCP中启用OAuth支持

要在MCP服务器中启用OAuth支持,我们首先需要配置OAuth提供者。MCP TypeScript-SDK提供了灵活的OAuth提供者接口:

import { McpServer } from '@modelcontextprotocol/sdk';
import { OAuthProvider, ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/auth';
import express from 'express';
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/auth/express';// 创建MCP服务器实例
const server = new McpServer({name: 'oauth-enabled-server',description: 'MCP服务器与OAuth集成示例',version: '1.0.0',
});// 创建一个OAuth提供者
const oauthProvider = new ProxyOAuthServerProvider({endpoints: {authorizationUrl: 'https://auth.example.com/oauth2/v1/authorize',tokenUrl: 'https://auth.example.com/oauth2/v1/token',revocationUrl: 'https://auth.example.com/oauth2/v1/revoke',// 其他相关端点...},// 验证访问令牌的方法verifyAccessToken: async (token) => {try {// 实现验证逻辑,可能涉及调用外部服务const response = await fetch('https://auth.example.com/oauth2/v1/introspect', {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded','Authorization': 'Basic ' + Buffer.from('client_id:client_secret').toString('base64')},body: `token=${token}`});const data = await response.json();if (data.active) {return {token,clientId: data.client_id,scopes: data.scope.split(' '),userId: data.sub,expiresAt: new Date(data.exp * 1000)};}return null; // 令牌无效} catch (error) {console.error('令牌验证失败:', error);return null;}},// 获取客户端信息的方法getClient: async (clientId) => {// 实现客户端查询逻辑return {client_id: clientId,redirect_uris: ['http://localhost:3000/callback'],// 其他客户端配置...};}
});// 配置服务器使用OAuth提供者
server.useOAuth(oauthProvider);// 为需要授权的资源添加保护
server.registerResource({name: 'protected-resource',description: '受OAuth保护的资源',requiresAuth: true, // 指定需要授权requiredScopes: ['read:resource'], // 指定所需的作用域resolve: async (params, context) => {// 授权用户信息在context.auth中可用const { auth } = context;return {content: `这是受保护的资源,只有授权用户可以访问。欢迎用户 ${auth.userId}!`,metadata: {accessInfo: {clientId: auth.clientId,scopes: auth.scopes,userId: auth.userId}}};}
});// 创建Express应用并设置OAuth路由
const app = express();// 使用MCP提供的Auth路由
app.use(mcpAuthRouter({provider: oauthProvider,issuerUrl: new URL('https://oauth.example.com'),baseUrl: new URL('https://api.example.com'),serviceDocumentationUrl: new URL('https://docs.example.com/oauth-setup'),
}));// 设置MCP服务器的HTTP传输
// ...其余代码省略

1.3 实现OAuth授权码流程

授权码流程是Web应用最常用的OAuth流程,下面我们实现一个完整的授权码流程:

import express from 'express';
import session from 'express-session';
import { McpServer } from '@modelcontextprotocol/sdk';
import { OAuthConfig, AuthorizationCodeFlow } from '@modelcontextprotocol/sdk/auth';const app = express();// 配置会话中间件
app.use(session({secret: 'your-session-secret',resave: false,saveUninitialized: false,cookie: { secure: process.env.NODE_ENV === 'production' }
}));// 创建MCP服务器
const server = new McpServer({name: 'oauth-server',description: 'OAuth授权码流程示例',version: '1.0.0',
});// OAuth配置
const oauthConfig: OAuthConfig = {clientId: 'your-client-id',clientSecret: 'your-client-secret',authorizationEndpoint: 'https://auth.example.com/oauth2/authorize',tokenEndpoint: 'https://auth.example.com/oauth2/token',redirectUri: 'http://localhost:3000/oauth/callback',scope: 'openid profile email',
};// 创建OAuth授权码流程
const authCodeFlow = new AuthorizationCodeFlow(oauthConfig);// 登录页面
app.get('/login', (req, res) => {// 生成并存储CSRF令牌const state = Math.random().toString(36).substring(2);req.session.oauthState = state;// 重定向到授权URLconst authUrl = authCodeFlow.getAuthorizationUrl({ state });res.redirect(authUrl);
});// OAuth回调
app.get('/oauth/callback', async (req, res) => {try {const { code, state } = req.query;// 验证状态参数,防止CSRF攻击if (state !== req.session.oauthState) {throw new Error('无效的状态参数,可能是CSRF攻击');}// 清除会话中的状态delete req.session.oauthState;// 交换授权码获取令牌const tokens = await authCodeFlow.exchangeCodeForTokens(code as string);// 存储令牌req.session.accessToken = tokens.access_token;req.session.refreshToken = tokens.refresh_token;req.session.tokenExpiry = Date.now() + tokens.expires_in * 1000;// 获取用户信息const userInfo = await fetchUserInfo(tokens.access_token);req.session.user = userInfo;// 重定向到应用主页res.redirect('/dashboard');} catch (error) {console.error('OAuth回调错误:', error);res.status(500).send('身份验证失败: ' + error.message);}
});// 受保护的路由中间件
function requireAuth(req, res, next) {if (!req.session.accessToken) {return res.redirect('/login');}// 检查令牌是否过期if (req.session.tokenExpiry < Date.now()) {// 实现令牌刷新逻辑return refreshAccessToken(req, res, next);}next();
}// 受保护的仪表盘页面
app.get('/dashboard', requireAuth, (req, res) => {res.send(`<h1>欢迎, ${req.session.user.name}!</h1><p>电子邮件: ${req.session.user.email}</p><a href="/logout">登出</a>`);
});// 登出
app.get('/logout', (req, res) => {req.session.destroy(err => {if (err) {console.error('会话销毁错误:', err);}res.redirect('/');});
});// 辅助函数:获取用户信息
async function fetchUserInfo(accessToken) {const response = await fetch('https://auth.example.com/oauth2/userinfo', {headers: {'Authorization': `Bearer ${accessToken}`}});if (!response.ok) {throw new Error('获取用户信息失败');}return response.json();
}// 辅助函数:刷新访问令牌
async function refreshAccessToken(req, res, next) {try {const newTokens = await authCodeFlow.refreshTokens(req.session.refreshToken);req.session.accessToken = newTokens.access_token;req.session.tokenExpiry = Date.now() + newTokens.expires_in * 1000;// 如果提供了新的刷新令牌,也更新它if (newTokens.refresh_token) {req.session.refreshToken = newTokens.refresh_token;}next();} catch (error) {console.error('令牌刷新失败:', error);req.session.destroy(err => {res.redirect('/login');});}
}// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`服务器运行在端口 ${PORT}`);
});

1.4 集成第三方OAuth提供商

MCP可以轻松集成常见的第三方OAuth提供商。以下是几个常见提供商的集成示例:

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词