客户端凭证模式(M2M)
客户端模式Client Credential主要用于一个应用后台调用另一个应用系统接口API资源,调用方在调用API资源前,需要先进行接口鉴权,如根据Client ID和Client Secret获取到令牌Access Token,然后调用方再携带令牌访API资源。
客户端模式非常适应于Machine-to-Machine(M2M)模式,比如通过后台服务发起应用的认证,而不是用户。
在M2M模式中,全程没有用户参与,仅在应用系统之间进行的授权操作。这种模式适用于需要机器之间进行自动化身份验证和授权的场景,如后台服务、API调用和数据同步等。
本文档介绍在竹云IDaaS平台中注册API消费者应用和API资源(API提供者),并对API消费者应用需要访问的API资源授权后,API消费者应用如何安全访问API资源的整体过程。
# 术语说明
M2M模式:Machine-to-Machine(机器对机器),简称M2M。
API消费者应用:作为API消费者(也叫做M2M应用),需要访问API资源来获取相关业务数据。
API资源:作为API提供者,通过开放API接口供API消费者进行调用。
# 授权流程
M2M模式流程:
- API消费者应用(M2M应用)使用Client ID、Client Secret向竹云IDaaS发起授权请求,竹云IDaaS验证Client ID、Client Secret通过后,返回颁发的Access Toke给API消费者应用。
- API消费者应用携带Access Token访问API资源,API资源服务器验证Access Token合法性、验证权限Scope列表是否在授权范围内。
- 资源服务器验证Access Token和权限Scope通过后, 返回API资源的详细信息。
# 开发步骤
竹云IDaaS平台采用 Oauth2.0 M2M模式接入开发流程如下:
# 注册API消费者应用
- 登录竹云IDaaS企业中心,选择导航栏
资源 > 应用
菜单进入应用列表,点击添加自建应用
按钮,填写应用名称后点击保存
按钮创建API消费者应用,点击进入应用详情
链接进入API消费者应用详情页面。 - 在应用详情界面点击
ClientSecret
右侧的启用
链接获取密钥,复制并保存平台分配的Client ID和Client Secret。 注意:竹云IDaaS系统不储存ClientSecret;获取到密钥后,请您妥善保管。
# 注册API资源(API提供者)并添加权限
登录竹云IDaaS企业中心,选择导航栏
资源 > 企业API
菜单进入API产品列表,点击添加自定义API产品
按钮,输入产品logo、产品名称、产品API标识后点击保存。 注意:API消费者应用获取IDaaS平台Token时,需要传递参数API资源定义的标识符,竹云IDaaS根据传递的产品API标识查找对应的API资源。进入企业API详情界面,点击
权限信息
进入API权限配置界面,点击右侧添加
按钮添加,输入权限代码、权限描述后点击保存。
# 授权API消费者应用并分配权限Scope
- 在API资源(API提供者)详情配置界面中,点击
应用授权
进入API应用授权界面,授权需要访问该API资源的消费者应用。 - 选择创建的“API消费者应用”, 点击进入应用配置界面。点击
API权限
菜单打开权限配置界面,选择已授权访问的API资源后,并分配所需的权限Scope。
# API消费者应用获取Token令牌
API消费者应用携带Client ID、Client Secret参数调用竹云IDaaS获取Token接口,竹云IDaaS验证通过后,返回Access Token和权限Scope列表。
# 请求说明
POST https://{your_domain}/api/v2/oauth2/token
# 请求头
参数名 | 中文名称 | 必须 | 示例值 | 描述 |
---|---|---|---|---|
Authorization | 认证信息 | 必须 | Basic UnFCMkhKdNOWk9xWA== | 使用client_id和client_secret进行basic64认证, 格式为: base64(client_id:client_secret) |
Content-Type | 数据类型 | 必须 | application/x-www-form-urlencoded | 使用表单方式提交参数 |
# 请求示例
POST https://{your_domain}/api/v2/oauth2/token
Authorization: Basic UnFCMkhKdGt6bFU...aT0NObkk4NlNOWk9xWA==
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&audience=https://apiprovider.com
# 请求参数
请求参数 | 是否必填 | 类型 | 说明 |
---|---|---|---|
grant_type | 是 | String | 固定值:client_credentials |
client_id | 是 | String | IDaaS分配给API消费者应用Client ID |
client_secret | 是 | String | IDaaS分配给API消费者应用Client Secret |
audience | 是 | String | 注册API资源时填写的标识符,推荐使用URL |
# 返回示例
正确返回示例
HTTP Status: 200 OK
{
"access_token": "eyJraWQiOiI3Yzc2ZWYxZTY0OWY0Yjc1OGVkZTczNGQ4ZDY4OWI5OSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL2JhbWJvb2Nsb3VkLmlkYWFzLXRlc3QtYWxwaGEuYmNjYXN0bGUuY29tL2FwaS92MS9vYXV0aDIiLCJhdWQiOiJtMm1hcGkiLCJleHAiOjE2ODc3NzM2OTEsImp0aSI6IlJGa01wWmFWbTQ5R3cyS1hHX2s0cFEiLCJpYXQiOjE2ODc3NzE4OTEsIm5iZiI6MTY4Nzc3MTc3MSwic3ViIjoiME1LZzFlM3dvaEJDc21Tck5Xbk80NjZHOTJ1Q0JmbEQiLCJhenAiOiIwTUtnMWUzd29oQkNzbVNyTlduTzQ2Nkc5MnVDQmZsRCJ9.TIL1WjzqRYdamTgIF591hTq8J08-PrZBRRDvxu9q88wLd5eHjEwfuamGQ2PmdMPXzJy7JCqX8Odr4Kpqlh04jwLYUv1vfIzApM2xjmd8MxU73uG9659PSKyf1yoP9_TLhDd30mgXLN2Fc7IgT1MAnQVTNYmlGU_JrRf-ECE44hMExDcGLScZF7xjJsWjAVX7Wzg4YiVTor3v4oGHdI2-NiEHMdOn2pIvWC_5mxCvIoVRWfYVcrRkpEkyBcWqnhNf422SMDitwkSBkVh73r1-zHOsGLUtci6zbaS2jWjN7OE1tA4iniHsgsx0HyzmfGGo9hLkD6kUpsawzjJH5uqSeg",
"token_type": "Bearer"
}
非法的客户端凭证错误示例
HTTP Status: 401
{
"error": "invalid_client",
"error_description": "Bad client credentials"
}
2
3
4
5
6
7
8
9
10
11
12
13
更多返回的错误码,请查看Oauth2.0协议错误码 (opens new window)
# 返回参数
返回参数 | 类型 | 说明 |
---|---|---|
access_token | String | IDaaS颁发给消费者应用的令牌 |
token_type | String | token类型,默认为Bearer Token |
# API消费者应用访问API资源
API消费者应用访问API资源时,请求头中需携带竹云IDaaS颁发的Access Token,请求头示例如:
Authorization:Bearer eyJraWQilJTMjU2In0.eyJpiOisRCJ9.TIL1WjwzjJH5uqSeg
注意:示例中请求头中的Access Token出于美观被格式化。
# API资源服务器(API提供者)验证Token
API资源服务器收到请求资源时,需校验Access Token和权限Scope,验证通过后返回给API消费者应用相关资源数据。
# Token示例说明
竹云IDaaS采用标准Oauth2.0协议颁布授权的Token令牌,Token令牌采用标准的JWT(JSON Web Token)进行封装, 拆包解码后的Access Token参数内容如下
{
"iss": "https://{yourdomain}/api/v1/oauth2",
"aud": "https://apiprovider.com",
"exp": 1687775036,
"jti": "sFZ-WBf2fj6zHvPxb6k12w",
"iat": 1687773236,
"nbf": 1687773116,
"sub": "0MKg1e3wohBCsmSrNWnO466G92uCBflD",
"scope": "add read delete",
"azp": "0MKg1e3wohBCsmSrNWnO466G92uCBflD"
}
2
3
4
5
6
7
8
9
10
11
# Token参数说明
参数名 | 类型 | 说明 |
---|---|---|
iss | String | Token签发者 |
aud | String | API资源(API提供者)定义的标识符 |
exp | String | Token过期时间点 |
jti | String | Token唯一标识 |
iat | String | Token签发时间点 |
nbf | String | Token生效时间点 |
sub | String | IDaaS分配给API消费者应用的Client ID |
scope | String | API权限Scope列表,多个权限列表之间使用空格分隔;若无权限时,不返回该参数。 |
azp | String | IDaaS分配给API消费者应用的Client ID |
# 验证Token
API资源服务器需要验证Token合法性和权限Scope,验证的步骤包含:
- 使用IDaaS公钥证书对Token进行验签;
- 验证Token中issuer参数是否合法;
- 验证Token中audience参数是否合法;
- 验证Token是否过期或失效;
- 验证权限Scope列表是否在授权范围内;
- 验证其它自定义参数......
以下是java语言使用JWT公钥验证id_token示例代码
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.jose4j.jwk.HttpsJwks;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver;
import org.jose4j.keys.resolvers.VerificationKeyResolver;
public class JwtVerificationExample {
public static void main(String[] args){
try {
//竹云IDaaS颁发的token
String idToken = "replace your token";
//竹云IDaaS JWT keys endpoint
String jwks_uri = "https://{your_domain}/api/v1/oauth2/keys";
//token颁发标识
String issuer = "https://{your_domain}/api/v1/oauth2";
//API资源定义的标识符
String audience = "replace your api identifier";
VerificationKeyResolver verificationKeyResolver = new HttpsJwksVerificationKeyResolver(new HttpsJwks(jwks_uri));
JwtConsumer consumer = new JwtConsumerBuilder().setVerificationKeyResolver(verificationKeyResolver)
.setRequireExpirationTime()
.setAllowedClockSkewInSeconds(300)
.setRequireSubject()
.setExpectedIssuer(issuer)
.setExpectedAudience(audience)
.build();
JwtClaims claims = consumer.processToClaims(idToken);
Map<String, Object> claimsMap = claims.getClaimsMap();
//获取API权限
Object scope = claimsMap.get("scope");
if (scope != null){
String[] apiArray = StringUtils.split((String) scope, " ");
//校验api权限是否满足,省略......
}
} catch (Exception e) {
//token验证失败处理
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46