如何用oauth4cj扩展新的授权服务器:10分钟手写一个自定义API类
【免费下载链接】oauth4cj一个Oauth开放授权协议库,Open Authorization的缩写项目地址: https://gitcode.com/Cangjie-TPC/oauth4cj
oauth4cj 是一个支持 OAuth 1.0 和 OAuth 2.0 的 Cangjie 开源授权协议库,允许第三方程序通过访问令牌访问受保护资源,而无需暴露用户凭据。想接入 Gitee、GitHub、微博、华为等任意授权服务器?本文带你10 分钟手写一个自定义 API 类,把任何 OAuth 授权服务器接入 oauth4cj 的授权码、密码、客户端、简化四种模式,全程只需继承一个基类、实现几个方法。
上图展示了 oauth4cj 遵循的标准 OAuth 授权流程:客户端(Application)向用户发起授权请求,经授权服务器(Authorization Server)颁发访问令牌(Access Token),最终调用资源服务器(Resource Server)获取受保护资源。你要做的,就是告诉 oauth4cj 你的授权服务器"端点在哪里、怎么认证、令牌怎么解析"——这正是自定义 API 类要回答的 4 个问题。
为什么扩展新授权服务器只需要写一个类
oauth4cj 采用"默认实现 + 策略扩展"的设计:
- oauth_service.cj 定义了 OAuth 服务的公共骨架(请求执行、关闭、HTTP 客户端等);
- default_api20.cj 是OAuth 2.0 的扩展基类,已内置绝大多数默认行为;
- 你只需继承它,把"每个平台不同的部分"填上即可。
基类中必须实现(abstract)的方法只有 2 个,其余方法都提供了默认实现,按需覆盖:
| 方法 | 作用 | 默认实现? |
|---|---|---|
getAccessTokenEndpoint() | 令牌请求端点 URL | ❌ 必须实现 |
getAuthorizationBaseUrl() | 用户授权页面 URL | ❌ 必须实现 |
getAccessTokenVerb() | 令牌请求的 HTTP 方法 | ✅ 默认 POST |
getRefreshTokenEndpoint() | 刷新令牌端点 | ✅ 默认同令牌端点 |
getClientAuthentication() | 客户端身份认证方式 | ✅ 默认 HTTP Basic |
getBearerSignature() | access_token 附加到请求的位置 | ✅ 默认放在 Header |
getAccessTokenExtractor() | 解析令牌响应体 | ✅ 默认 JSON 解析 |
getDeviceAuthorizationEndpoint() | 设备授权端点(RFC 8628) | ❌ 不支持则抛异常 |
💡 规律总结:一个平台的 OAuth 文档里通常只写 4 个信息——授权 URL、令牌 URL、认证方式、响应格式。这 4 个信息恰好对应上表的 4 个方法。
10分钟手写自定义API类:完整步骤
第 1 步:阅读目标平台的 OAuth 文档
打开你的授权服务器(下例假设是某平台oauth.example.com)的开发者文档,抄下 4 个信息:
- 授权页面:
https://oauth.example.com/authorize - 令牌端点:
https://oauth.example.com/oauth/token(POST) - 客户端认证:
client_id/client_secret放在请求体中 - 令牌响应:标准 JSON(
access_token、expires_in、refresh_token…)
第 2 步:继承 DefaultApi20 写你的 API 类
只需 30 行代码(对照 default_api20.cj 的方法签名):
/* * 准备 OAuth 2.0 的默认实现: * 继承 DefaultApi20,实现抽象方法,覆盖不同的提取器或服务。 */ public class ExampleApi <: DefaultApi20 { protected init() { } public static func instance(): ExampleApi { return ExampleApi() } //返回请求类型(默认就是 POST,这里显式写出让读者直观) public func getAccessTokenVerb(): Verb { return Verb.POST } //接收访问令牌请求的 URL public func getAccessTokenEndpoint(): String { return "https://oauth.example.com/oauth/token" } //用户授权页面链接 protected func getAuthorizationBaseUrl(): String { return "https://oauth.example.com/authorize" } //客户端认证:client_id/client_secret 写入请求主体 public func getClientAuthentication(): ClientAuthentication { return RequestBodyAuthenticationScheme.instance() } //access_token 通过 URI 查询参数附加到请求中 public func getBearerSignature(): BearerSignature { return BearerSignatureURIQueryParameter.instance() } }注意两点:
getAccessTokenExtractor()不用写——默认返回 OAuth2AccessTokenJsonExtractor,绝大多数平台返回的都是标准 JSON;- 你的平台若要求 token 放在 Header(
Authorization: Bearer xxx),则getBearerSignature()可省略,默认即是该行为,见 bearer_signature.cj。
第 3 步:用 ServiceBuilder 构建服务
在 service_builder_oauth20.cj 的建造者模式中,把自定义 API 类作为build()参数传入:
let service: OAuth20Service = ServiceBuilderOAuth20(clientId). setApiSecret(clientSecret). setDefaultScope("profile emails"). setCallback(redirect_uri). build(ExampleApi.instance()) // 👈 关键:注入自定义 API 类第 4 步:跑通授权码模式(以 oauth20_service.cj 的接口为例)
//1. 生成可获取授权码的链接 let authorizationUrl: String = AuthorizationUrlBuilder(service). setState("secret").build() //2. 用回调拿到的一次性授权码 code,换取访问令牌 let accessToken: OAuth2AccessToken = service.getAccessToken(code) //3. 签名并请求受保护资源 let request: OAuth4cjRequest = OAuth4cjRequest(Verb.GET, protectedResourceUrl) service.signRequest(accessToken, request) let response: OAuth4cjResponse = service.execute(request)至此,你的授权服务器已完整接入。其他模式同样直接可用:
- 密码模式:
service.getAccessTokenPasswordGrant(username, password) - 客户端模式:
service.getAccessTokenClientCredentialsGrant() - 刷新令牌:
service.refreshAccessToken(accessToken.getRefreshToken()) - 设备授权(RFC 8628):覆盖
getDeviceAuthorizationEndpoint()后调用service.getDeviceAuthorizationCodes()
平台差异速查:该覆盖哪个方法
| 平台差异 | 需要覆盖的方法 |
|---|---|
令牌响应不是 JSON(如简化模式返回access_token=xxx&expires_in=xxx纯文本) | getAccessTokenExtractor(),参考 oauth2_access_token_extractor.cj |
| 刷新令牌端点独立于令牌端点 | getRefreshTokenEndpoint() |
| 支持令牌吊销(RFC 7009) | getRevokeTokenEndpoint() |
| 令牌请求要求 GET(如腾讯简化模式) | getAccessTokenVerb()返回Verb.GET |
| 支持设备授权授予(RFC 8628) | getDeviceAuthorizationEndpoint() |
官方文档中的 Gitee、华为、腾讯示例就是同一套模板的不同填法,可直接在 README.md 的"功能示例"章节对照阅读。
常见问题
Q1:自定义 API 类要单例吗?不是必须,但示例代码统一提供了static func instance()方便复用。
Q2:OAuth 1.0a 平台怎么扩展?同样思路:继承 default_api10a.cj 基类,用 service_builder_oauth10a.cj 构建服务。
Q3:请求失败报 401 通常是哪里出了问题?优先检查getClientAuthentication()与平台要求的认证方式是否一致(Body 传参 vs HTTP Basic 头)。
参考资料
- 完整接口说明:doc/feature_api.md
- 各平台功能示例与编译构建说明:README.md
- 版本变更记录:CHANGELOG.md
动手试试吧:找到你常用的一个授权平台的 OAuth 文档,照上表填 4 个信息,10 分钟后你就会得到一个能跑通授权码模式的自定义 API 类。
【免费下载链接】oauth4cj一个Oauth开放授权协议库,Open Authorization的缩写项目地址: https://gitcode.com/Cangjie-TPC/oauth4cj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考