如果你有经常使用 OpenAI 或者 HuggingFace 这一类面向开发者的 SaaS 服务,对于 API Key 肯定不会陌生。我们在使用这些服务时,通常都会在其平台上面创建一套 API Key,之后我们才能在代码中通过这一串 API key 访问其服务;同时,我们购买的权益也会和对应的 API Key 绑定在一起。
除了 API Key 的校验,用户认证也是重要的一环,如果能够快速集成市面上常见的社会化登录方式(如微信、GitHub、Google 等),这将会极大降低用户使用的门槛。而大模型服务往往需要快速上线,快速强占市场,从零开发一套完整的用户认证体系无疑会消耗大量的时间和人力成本。
这篇文章我们关注的问题是:假如你也有这样一个面向开发者的大模型服务,我们如何基于 Authing强大的认证以及数据资源能力,快速实现你的认证和 API Key 模块。
一个比较完备且用户体验良好的登录框,应该能够支持以下几个基本的认证方式:账密登录、手机号验证码登录、常见社会化登录;如果是有公众号推广需求的服务,可能还需要关注公众号登录。而这些能力,Authing 的 Guard 全部都内置好了,不需要进行任何的开发工作。
首先第一步,我们需要在 Authing 创建一个用户池,这里我们将用户池类型设置为 ToC:
接下来在 Authing 控制台的应用 - 自建应用页面创建一个应用:这里我们选择「标准 Web 应用」,并且填好应用名称和二级域名:
创建好之后,你将会自动拥有一个能够被立即在线访问到的登录页面。接下来要做的,就是在这上面配置你需要的登录方式了:在应用详情的「登录控制」菜单,你可以快速添加你需要的认证方式:
我们以微信扫码登录这个方式举例:我们提供了非常完整的文档,你只需要按照文档的说明,填入相关的 AppId、AppSecret 即可:
配置完成再次访问登录页面,就可以看到微信扫码登录已经出现在界面上了!That's It!
如果你对于登录页面有品牌化的需求,比如自定义背景、自定义 CSS 等,也可以在 Authing 控制台的品牌化页面进行自定义:
接下来我们测试一下登录:用一个账号登录之后,你会看到页面跳转到了一个欢迎界面,这个是一个默认的调试页面,这里面介绍了后续你需要完成的操作:
如果你遇到了「无权限登录,请联系管理员」的错误提示,可以在 Authing 控制台的安全设置 - 通用安全 - 注册安全页面,将「禁止注册」开关关闭,以及在应用详情的访问授权页面,设置应用访问控制规则为「所有人可访问」。
请重点关注「处理回调」这部分的内容:相信你也已经猜到了,实际我们业务上线的时候,是需要替换这个默认的调试页面成你自己的回调地址的,「处理回调」这部分介绍了后续你需要完成的内容:
1. 替换回调地址;
2. 在回调地址接收 Authing 传过来的授权码 code;
3. 使用 code 换取用户信息和 token 并存储 token;
4. 后续此用户请求你的 API 时,需要携带上此 token;
5. 在后端校验此 token 的合法性。
如果你希望将登录页面嵌入到你自己的页面中,我们也提供了 React、Vue、Angular 以及 iOS、Android 的组件:
如本文最开始介绍的:大模型服务一般都会通过 API Key 的方式,允许用户通过可编程的方式接入你的大模型服务,以 OpenAI 举例,我们都知道在 Python 程序中我们是这么去使用的:
import openai
# 设置 API Key
openai.api_key = 'xxxxxxxxxxxxxxxxxxxxxx';
# 接下来去请求 OpenAI 的相关接口
这里面背后的逻辑,是 OpenAI 的 Python SDK 在请求 OpenAI 接口的时候,会在请求头上携带此 token,然后在后端校验此 Token 的合法性以及权益。
下面我们介绍如何使用 Authing 的数据资源能力来实现这两部分功能:API Key 的鉴权、API Key 的权益校验。
创建权限空间
第一步,我们在 Authing 控制台的「权限管理」-「权限空间」菜单创建一个名为 apikey 的权限空间,我们使用这个空间来存储和管理平台所有的 API Key。
创建 API Key 数据资源
假设我们想象你的开发者在你的平台上面创建了一个 API key,这个 key 可能会包含以下基本的信息:
与之对应的,Authing 提供的了「数据资源」的概念,完美契合这样的场景。我们在 Authing 控制台的「权限管理」-「数据资源权限」菜单创建一个示例的字符串类型的「数据资源」:
{
"CREATED": "2023.10.22 10:00:00",
"LAST_USED": "2023.10.22 20:00:00",
"TOTAL_USAGE": 123,
"USAGE_AVALIABLE": 1000
}
除了控制台之外,我们也提供了相关的 API 和 SDK,帮助你在程序中自动创建 API Key(数据资源),下面以 nodejs sdk 为例:
import { ManagementClient } from "authing-node-sdk";
// 初始化 ManagementClient
const managementClient = new ManagementClient({
accessKeyId: "AUTHING_ACCESS_KEY_ID", // Authing Access Key ID
accessKeySecret: "AUTHING_ACCESS_KEY_SECRET", // Authing Access Key Secret
});
const resource = await managementClient.createDataResource({
"namespaceCode": "apikey",
"resourceName": "My API Key",
"resourceCode": "fizePoEnkNrvWPwOxeYPdbQjoFXJfppDtm",
"type": "STRING",
"struct": '{"CREATED": "2023.10.22 10:00:00", "LAST_USED": "2023.10.22 20:00:00", "TOTAL_USAGE": 123, "USAGE_AVALIABLE": 1000}',
"actions": [revoke]
})
console.log(resource)
给用户授权 API Key 数据资源
假如你在通过 Authing 来管理用户,我们推荐在创建好 API Key 之后,将这个 API Key 授权给对应的用户,这样你可以在 Authing 平台清晰得看到所有用户的 API Key 授权情况。如果你只是使用 Authing 的数据资源来做 API Key 管理,而不使用 Authing 来管理用户,此部分可跳过。
在 Authing 控制台的「权限管理」 - 「数据资源权限」菜单,点击右上角的「授权」按钮,我们先创建一个针对之前创建的 API Key 的数据策略:
接着将这个数据策略授权给某个用户:
在这里我们之所以使用数据策略这种将数据资源打包的方式来对用户进行授权,是为了后续的扩展性和可维护性,比如你可能还会需要给某个角色统一授权某一些打包在一起的资源,当用户订阅到期之后,将其移除出角色,这样就可以自动取消授权。
之后,你就可以看到这个用户已经被授权了这个 API Key 了:
同样的,除了控制台之外,我们也提供了相关的 API 和 SDK,下面以 nodejs sdk 为例:
import { ManagementClient } from "authing-node-sdk";
// 初始化 ManagementClient
const managementClient = new ManagementClient({
accessKeyId: "AUTHING_ACCESS_KEY_ID", // Authing Access Key ID
accessKeySecret: "AUTHING_ACCESS_KEY_SECRET", // Authing Access Key Secret
});
// 创建数据策略
const dataPolicy = await managementClient.createDataPolicy({
policyName: "APIKEY_AUTHZ",
statementList: [
{
effect: "ALLOW",
permissionList: [
{
resourceCode: "fizePoEnkNrvWPwOxeYPdbQjoFXJfppDtm",
operationType: "ALL"
},
],
namespaceCode: "apikey",
},
]
});
// 授权数据策略
await managementClient.authorizeDataPolicy({
policyIds: [dataPolicy.id],
targetList: [
{
type: "USER",
id: "user_id"
}
]
})
// 获取用户权限列表
const userPermissionList = await managementClient.getUserPermissionList({
"userIds": ["user_id"]
}
在项目代码中使用
下面我们将以 Node.js 的 NestJS Web 框架举例。
假设你需要用户在请求你的 API 时,在 x-myllm-apikey 请求头上需要带上 API Key,我们封装一个 NestJS 的 Guard,在这里完成 API Key 的校验逻辑:
从请求头中获取 x-myllm-apikey 的内容;
判断此 API Key 对应的数据资源是否存在,如果不存在,返回 false;
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { ManagementClient } from "authing-node-sdk";
// 初始化 ManagementClient
const managementClient = new ManagementClient({
accessKeyId: "AUTHING_ACCESS_KEY_ID", // Authing Access Key ID
accessKeySecret: "AUTHING_ACCESS_KEY_SECRET", // Authing Access Key Secret
});
@Injectable()
export class AuthGuard implements CanActivate {
constructor() {}
async canActivate(context: ExecutionContext): Promise {
const apiKey = request.headers['x-myllm-apikey'];
const existsRes = await managementClient.checkDataResourceExists({
namespaceCode: "apikey",
resourceCode: apiKey
});
return existsRes.data.exists;
}
接着在对应的接口中,应用此 Guard 即可:
@UseGuards(AuthGuard)
@Post('/my-api')
public async handleApi() {}
假如说你的大模型服务有以下几种订阅版本:
订阅版本 | Rate Limit | 可用的 Token 数 | 其他权益明细 |
---|---|---|---|
免费版 | 3 / s | 50000 | ... |
基础版 | 10 / s | 500000 | ... |
企业版 | Unlimited | Unlimited | ... |
你可以借助 Authing 的数据资源能力,将 API Key 对应的所有权益全部存储起来:比如 TOTAL_USAGE(已经使用的 token 数)、USAGE_AVALIABLE(可用的 Token 数),以及其他任何你需要存储的数据。
你可以调用 Authing 数据资源的创建、查询、修改、删除 API,对这些 API Key 进行管理。
比如我们修改一下上一节的 AuthGuard 的实现,这里我们加入了可用 Token 数的权益校验逻辑:
获取数据资源的详情;
在这里例子中我们在数据资源的内容里面添加了两个自定义字段 USAGE_AVALIABLE和 TOTAL_USAGE,所以我们的判断依据就是比较以下这两个大小即可。如果说你的业务系统有其他的判断规则,你也可以写任意的自定义逻辑。
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { ManagementClient } from "authing-node-sdk";
// 初始化 ManagementClient
const managementClient = new ManagementClient({
accessKeyId: "AUTHING_ACCESS_KEY_ID", // Authing Access Key ID
accessKeySecret: "AUTHING_ACCESS_KEY_SECRET", // Authing Access Key Secret
});
@Injectable()
export class AuthGuard implements CanActivate {
constructor() {}
async canActivate(context: ExecutionContext): Promise {
const apiKey = request.headers['x-myllm-apikey'];
const existsRes = await managementClient.checkDataResourceExists({
namespaceCode: "apikey",
resourceCode: apiKey
});
return existsRes.data.exists;
if (!existsRes.data.exists) {
return false
}
const { data: resource } = await managementClient.getDataResource({
namespaceCode: "apikey",
resourceCode: apiKey
})
const { struct } = resource;
const apiKeyData = JSON.parse(struct);
const { TOTAL_USAGE, USAGE_AVALIABLE } = apiKeyData;
return USAGE_AVALIABLE > TOTAL_USAGE
}
}
如果你需要在每次请求之后增加一下 TOTAL_USAGE 的值,你只需要调用修改数据资源的接口即可。