@mcp-abap-adt/auth提供者

MCP ABAP ADT身份验证代理的令牌提供者。
此包为提供令牌提供者实现 @mcp-abap-adt/auth-broker 包裹。
安装
npm install @mcp-abap-adt/auth-providers概述
此包实现了 ITokenProvider 接口来自 @mcp-abap-adt/interfaces:
- 授权码提供者 -使用基于浏览器的OAuth2授权代码流(用户令牌)
- 客户端认证提供者 -用途
client_credentials授权类型(无需浏览器)
提供者是通过构造函数配置的; getTokens() 不接受任何参数,并在内部处理刷新/登录。
责任和设计原则
核心开发原则
仅接口通信该方案遵循一个基本的发展原则: 所有与外部依赖关系的交互都只能通过接口进行代码知道 没有超出接口中定义的内容.
这意味着:
- 不知道其他包中的具体实现类
- 不了解接口中未定义的内部数据结构或方法
- 不假设接口契约之外的实现行为
- 不访问接口中未明确定义的属性或方法
这一原则确保:
- 松散结合:提供者与其他包中的具体实现解耦
- 灵活性:可以添加新的实现,而无需修改提供程序
- 可测试性:易于模拟测试依赖关系
- 可维护性:对实现的更改不会影响提供者
包装责任
该包负责:
- 实现令牌提供者接口:提供以下内容的具体实施
ITokenProvider接口定义于@mcp-abap-adt/interfaces - 代币获取:处理OAuth2流(基于浏览器、刷新令牌、客户端凭据)以获取JWT令牌
- 令牌验证:通过检查exp声明(无HTTP请求)在本地验证JWT
- OAuth2流:管理基于浏览器的OAuth2授权代码流和刷新令牌流
这个包有什么作用
- 实现ITokenProvider:提供具体实施(
AuthorizationCodeProvider,ClientCredentialsProvider) - 处理OAuth2流:基于浏览器的OAuth2、刷新令牌和客户端凭据授予类型
- 获得代币:向UAA端点发出HTTP请求以获取JWT令牌
- 验证令牌:通过检查exp声明(无HTTP请求)在本地验证JWT
- 返回令牌:退货
ITokenResult随着authorizationToken可选refreshToken
此软件包不做什么
- 不存储令牌:令牌存储由以下人员处理
@mcp-abap-adt/auth-stores - 不编排身份验证:令牌生命周期管理由以下人员处理
@mcp-abap-adt/auth-broker - 不知道维修钥匙:服务密钥加载由商店处理
- 不管理会话:会话管理由商店处理
- 不返回
serviceUrl如果未知:供应商可能不会返回serviceUrl因为它们只处理令牌获取,不处理连接配置
外部依赖
此包与外部包交互 仅通过接口:
@mcp-abap-adt/auth-broker:使用接口(ITokenProvider,IAuthorizationConfig)-不知道AuthBroker实施@mcp-abap-adt/logger:用途Logger日志记录接口-不了解具体的日志记录器实现@mcp-abap-adt/connection:使用连接实用程序进行令牌验证-通过定义良好的函数进行交互- 不直接依赖商店:与商店的所有交互都是通过消费者传递的接口进行的
用法
基本用法
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { AuthorizationCodeProvider, ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';
// User token via authorization_code (browser flow)
const authCodeBroker = new AuthBroker({
tokenProvider: new AuthorizationCodeProvider({
uaaUrl: 'https://...',
clientId: '...',
clientSecret: '...',
browser: 'system',
}),
});
// Service token via client_credentials (no browser)
const clientCredsBroker = new AuthBroker({
tokenProvider: new ClientCredentialsProvider({
uaaUrl: 'https://...',
clientId: '...',
clientSecret: '...',
}),
}, 'none');SSO提供者
该软件包还包括用于OIDC和SAML2的SSO提供程序,以及一个用于DI友好创建的小型工厂。
可用提供商:
OidcBrowserProvider(授权码+PKCE)OidcDeviceFlowProviderOidcPasswordProviderOidcTokenExchangeProviderSaml2BearerProvider(SAML断言交换)Saml2PureProvider(返回SAMLResponse作为令牌)
工厂示例:
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { SsoProviderFactory } from '@mcp-abap-adt/auth-providers';
const tokenProvider = SsoProviderFactory.create({
protocol: 'oidc',
flow: 'browser',
config: {
issuerUrl: 'https://example-idp/.well-known/openid-configuration',
clientId: '...',
clientSecret: '...',
scopes: ['openid', 'profile', 'email'],
browser: 'system',
},
});
const broker = new AuthBroker({ tokenProvider }, 'none');OIDC浏览器示例(手动代码+显式端点):
import { OidcBrowserProvider } from '@mcp-abap-adt/auth-providers';
const provider = new OidcBrowserProvider({
clientId: '...',
tokenEndpoint: 'https://issuer/oauth/token',
authorizationEndpoint: 'https://issuer/oauth/authorize',
authorizationCode: '
',
redirectUri: 'urn:ietf:wg:oauth:2.0:oob',
});SAML承载示例(手动流程):
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { Saml2BearerProvider } from '@mcp-abap-adt/auth-providers';
const provider = new Saml2BearerProvider({
assertionFlow: 'manual',
idpSsoUrl: 'https://idp.example.com/sso',
spEntityId: 'my-sp-entity',
uaaUrl: 'https://uaa.example.com',
clientId: '...',
clientSecret: '...',
});
const broker = new AuthBroker({ tokenProvider: provider }, 'none');SAML承载示例(无头,断言提供者):
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { Saml2BearerProvider } from '@mcp-abap-adt/auth-providers';
const provider = new Saml2BearerProvider({
assertionFlow: 'assertion',
assertionProvider: async () => {
return getSamlResponseFromSsoProxy();
},
uaaUrl: 'https://uaa.example.com',
clientId: '...',
clientSecret: '...',
});
const broker = new AuthBroker({ tokenProvider: provider }, 'none');纯SAML示例(基于cookie):
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { Saml2PureProvider } from '@mcp-abap-adt/auth-providers';
const provider = new Saml2PureProvider({
assertionFlow: 'manual',
idpSsoUrl: 'https://idp.example.com/sso',
spEntityId: 'my-sp-entity',
// Convert SAMLResponse to session cookies for SAP (implementation-specific)
cookieProvider: async (samlResponse) => {
return exchangeSamlForCookies(samlResponse);
},
});
const broker = new AuthBroker({ tokenProvider: provider }, 'none');与商店
重要:BTP和ABAP是不同的实体:
- 业务流程平台 (基础BTP)-用途
BtpServiceKeyStore和BtpSessionStore(无sapUrl) - ABAP -用途
AbapServiceKeyStore和AbapSessionStore(与sapUrl)
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { AuthorizationCodeProvider, ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';
import {
XsuaaServiceKeyStore,
XsuaaSessionStore,
BtpServiceKeyStore,
BtpSessionStore,
AbapServiceKeyStore,
AbapSessionStore
} from '@mcp-abap-adt/auth-stores';
// XSUAA provider with stores (client_credentials or auth code)
const xsuaaServiceKeyStore = new XsuaaServiceKeyStore('/path/to/service-keys');
const xsuaaSessionStore = new XsuaaSessionStore('/path/to/sessions');
const xsuaaBroker = new AuthBroker({
serviceKeyStore: xsuaaServiceKeyStore,
sessionStore: xsuaaSessionStore,
tokenProvider: new ClientCredentialsProvider({
uaaUrl: 'https://...',
clientId: '...',
clientSecret: '...',
}),
}, 'none');
// BTP provider with stores (base BTP, without sapUrl)
const btpServiceKeyStore = new BtpServiceKeyStore('/path/to/service-keys');
const btpSessionStore = new BtpSessionStore('/path/to/sessions');
const btpBroker = new AuthBroker({
serviceKeyStore: btpServiceKeyStore,
sessionStore: btpSessionStore,
tokenProvider: new AuthorizationCodeProvider({
uaaUrl: 'https://...',
clientId: '...',
clientSecret: '...',
browser: 'system',
}),
});
// ABAP provider with stores (with sapUrl)
const abapServiceKeyStore = new AbapServiceKeyStore('/path/to/service-keys');
const abapSessionStore = new AbapSessionStore('/path/to/sessions');
// Use custom port if running alongside other services (e.g., proxy on port 3001)
const abapBroker = new AuthBroker({
serviceKeyStore: abapServiceKeyStore,
sessionStore: abapSessionStore,
tokenProvider: new AuthorizationCodeProvider({
uaaUrl: 'https://...',
clientId: '...',
clientSecret: '...',
browser: 'system',
redirectPort: 4001,
}), // Custom port to avoid conflicts
});令牌提供商
授权码提供者
使用基于浏览器的OAuth2流或刷新令牌:
import { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';
const provider = new AuthorizationCodeProvider({
uaaUrl: 'https://...authentication...hana.ondemand.com',
clientId: '...',
clientSecret: '...',
browser: 'system',
});
// If refreshToken is provided here, uses refresh flow (no browser)
// Otherwise, opens browser for OAuth2 authorization
const result = await provider.getTokens();
// result.authorizationToken contains the JWT token
// result.refreshToken contains refresh token (if browser flow was used)客户端认证提供者
用途 client_credentials 授权类型-无需浏览器交互:
import { ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';
const provider = new ClientCredentialsProvider({
uaaUrl: 'https://...authentication...hana.ondemand.com',
clientId: '...',
clientSecret: '...',
});
const result = await provider.getTokens();
// result.authorizationToken contains the JWT token
// result.refreshToken is undefined (client_credentials doesn't provide refresh tokens)备注:The browserAuthPort 参数(默认值:3001)配置OAuth回调服务器端口。如果请求的端口已在使用中,将抛出错误。在开始身份验证之前,您必须指定其他端口或释放该端口。服务器在身份验证完成后正确关闭所有连接并释放端口,确保没有持久的端口占用。
超时:浏览器身份验证有30秒的超时时间,以防止阻止消费者。如果身份验证未在30秒内完成,则操作将失败并出现超时错误。这可以防止在用户未完成身份验证时提供程序无限期挂起。
流程终止处理:OAuth回调服务器为以下对象注册清理处理程序 SIGTERM, SIGINT, SIGHUP,以及 exit 信号。这确保了即使MCP客户端(如Cline)在身份验证完成之前终止进程,端口也能正确释放。这对于stdio服务器尤其重要,因为客户端可能随时终止进程。在Windows上 SIGBREAK 信号(Ctrl+Break)也被处理。
跨平台浏览器支持:浏览器身份验证适用于Linux、macOS和Windows:
- Linux:自动设置
DISPLAY=:0如果两者都没有DISPLAY也不WAYLAND_DISPLAY设置环境变量。支持多个浏览器可执行文件名(google-chrome,google-chrome-stable,chromium,chromium-browserChrome;firefox,firefox-esrFirefox)。 - 视窗:使用得当
cmd /c start ""用于可靠打开浏览器的语法。 - macOS:使用本地
open -a命令。
无头模式(SSH/远程):对于没有显示器的环境(SSH会话、Docker、CI/CD),请使用 browser: 'headless':
const result = await provider.getTokens();在无头模式下,记录身份验证URL,服务器等待用户手动完成身份验证。用户可以在任何机器上打开URL,服务器将收到回调。
浏览器选项:
'system'(默认):打开系统默认浏览器'headless':记录URL,等待手动回调(SSH/remote)'none':记录URL,立即拒绝(自动测试)'chrome','edge','firefox':打开特定浏览器
令牌验证
供应商可以执行 本地JWT验证 通过检查 exp (到期)索赔:
const isValid = await provider.validateToken(token, serviceUrl);- 未向SAP服务器发出HTTP请求
- 退货
true如果令牌具有有效的JWT格式,并且exp未来(60秒缓冲) - 退货
false如果令牌已过期、格式无效或将在60秒内过期 - 网络问题(ECONNREFUSED、超时)不会触发令牌刷新
- HTTP错误(401/403)由中的重试机制处理
makeAdtRequest包装器
// Local validation (no HTTP)
const provider = new AuthorizationCodeProvider({
uaaUrl: 'https://...authentication...hana.ondemand.com',
clientId: '...',
clientSecret: '...',
});
const isValid = await provider.validateToken(token); // serviceUrl optional
// Checks JWT exp claim locally, no network request在以下情况下,这种方法可以防止不必要的令牌刷新和浏览器身份验证:
- 服务器无法访问(ECONNREFUSED,超时)
- 网络速度慢或不稳定
- 在离线/断开连接模式下运行
令牌刷新
提供者在内部自动处理刷新 getTokens()。不需要单独的刷新方法。
try {
const result = await provider.getTokens();
// Returns new access token and refresh token (if available)
} catch (error) {
if (error instanceof ValidationError) {
console.error('Missing fields:', error.missingFields);
} else if (error instanceof RefreshError) {
console.error('Browser auth failed:', error.cause);
}
}错误处理
该包提供了类型化的错误类,以更好地处理错误:
import {
TokenProviderError,
ValidationError,
RefreshError,
SessionDataError,
ServiceKeyError,
BrowserAuthError,
} from '@mcp-abap-adt/auth-providers';
try {
const result = await provider.getTokens();
} catch (error) {
if (error instanceof ValidationError) {
// provider config validation failed
console.error('Missing required fields:', error.missingFields);
console.error('Error code:', error.code); // 'VALIDATION_ERROR'
} else if (error instanceof RefreshError) {
// Token refresh operation failed
console.error('Refresh failed:', error.message);
console.error('Original error:', error.cause);
console.error('Error code:', error.code); // 'REFRESH_ERROR'
} else if (error instanceof BrowserAuthError) {
// Browser authentication failed
console.error('Browser auth failed:', error.cause);
}
}错误类型:
TokenProviderError-基类与code: string财产ValidationError-提供程序配置验证失败,包括missingFields: string[]RefreshError-令牌刷新失败,包括cause?: ErrorSessionDataError-会话数据无效,包括missingFields: string[]ServiceKeyError-服务密钥数据无效,包括missingFields: string[]BrowserAuthError-浏览器身份验证失败,包括cause?: Error
所有错误代码均在中定义 @mcp-abap-adt/interfaces 包装为 TOKEN_PROVIDER_ERROR_CODES.
测试
该包包括单元测试(带模拟)和集成测试(带真实文件和服务)。
单元测试
npm test集成测试
集成测试使用来自的真实文件 tests/test-config.yaml:
- 复制
tests/test-config.yaml.template到tests/test-config.yaml - 填写真实目的地名称
- 运行测试-如果已配置,集成测试将使用真实服务
# Destination name (used for service key file: .json and session file: .env)
destination: "trial" # Example: "trial" -> looks for trial.json and trial.env
# Optional: Destination directory (base directory for service keys and sessions)
# If not specified, uses default platform paths:
# Unix: ~/.config/mcp-abap-adt
# Windows: %USERPROFILE%\Documents\mcp-abap-adt
# Uncomment and set if you need a custom path:
# destination_dir: ~/.config/mcp-abap-adt如果出现以下情况,集成测试将跳过 test-config.yaml 未配置或包含占位符值。
测试场景:
- 场景1和2:令牌生命周期-通过浏览器登录并重用之前场景中的令牌
- 情景3:会话过期+刷新令牌过期-提供者应通过浏览器重新进行身份验证
- 令牌验证:在所有情况下明确验证令牌过期
备注:
- 集成测试使用
AbapServiceKeyStore和AbapSessionStore用于加载服务密钥和会话 - 如果没有可用的刷新令牌,测试可能会打开浏览器进行身份验证。这是预期的行为。
- 每个测试场景都使用一个唯一的端口(3101、3102、3103)来避免端口冲突
- 测试使用
browser: 'system'用于交互式身份验证(不是'none')
调试日志记录
要在测试或运行时启用详细的日志记录,请设置环境变量:
# Enable logging for auth providers (short name)
DEBUG_PROVIDER=true npm test
# Or use long name (backward compatibility)
DEBUG_AUTH_PROVIDERS=true npm test
# Or enable via general DEBUG variable
DEBUG=true npm test
# Or include in DEBUG list
DEBUG=provider npm test
# Or
DEBUG=auth-providers npm test
# Set log level (debug, info, warn, error)
LOG_LEVEL=debug npm test日志记录用途 @mcp-abap-adt/logger 带有结构化日志记录的软件包:
- 代币交换阶段(我们发送什么,我们接收什么)
- 令牌信息(长度、预览、过期)
- 令牌验证检查(过期、有效性)
- 详细信息错误
输出示例:
[INFO] ℹ️ [browserAuth] Exchanging code for token...
[INFO] ℹ️ Tokens received: accessToken(2263 chars), refreshToken(34 chars)
[DEBUG] 🐛 [BaseTokenProvider] Token validation check {"expiresAt":"2025-12-25 11:08:15 UTC","isValid":true}
[INFO] ℹ️ [browserAuth] Authorization URL: https://.../oauth/authorize?...
[INFO] ℹ️ [browserAuth] Browser: system日志记录功能:
- 令牌格式:为了安全起见,令牌以截断格式(开始…结束)记录
- 日期格式:过期日期以可读格式(YYYY-MM-DD HH:MM:SS UTC)显示,而不是ISO格式
- 浏览器信息:记录浏览器类型和授权URL以进行调试
- 代币生命周期:令牌获取、验证和刷新操作的详细日志记录
依赖项
@mcp-abap-adt/interfaces(^0.2.2)-接口定义和错误代码常量axios-HTTP客户端express-OAuth2回调服务器open-浏览器打开实用程序
许可证
麻省理工学院
