更新时间:2019-11-28 16:55
在应用托管体系中,并未所有应用的分发模式都是按照Kubernetes方式,对每一次应用向最终用户的交付形态,都是交付一个真实的物理运行空间。还有这里要提到的SaaS类应用。此类应用,其分发模式,仅仅是在原有系统中,分配了一个账户,交付给客户。因此,为了兼容单租户模式的独立托管部署和这类多租户的SaaS系统,平台抽象了与这类SaaS系统的交互行为,应用为满足这个交互行为,需要完成本文所提的对接要求。
IoT平台管理阿里云租户与应用内租户的映射关系(支持一个阿里云租户拥有同一个应用的多个租户),SaaS管理应用内部的租户结构
本期无需对接IoT平台的组织架构体系,SaaS内部自行管理组织架构,但是两边的组织架构需要能够定位到同一个员工身份, 目前通过员工电话作为识别的唯一标识;
IoT平台管理租户及其组织架构中的员工是否有权限访问应用,SaaS管理租户及组织架构中员工访问功能列表;
应用部署在应用托管平台中,系统会为部署的每个应用颁发AppKey,AppKey通过环境变量方式注入至业务代码运行的容器中,供代码获取并使用;
这种场景适用于您自行通过外置服务器的方式来搭建SaaS服务的模式。创建AppKey的过程如下问:
登录卖家中心,在“应用接入”->”应用管理”->“创建应用”账号分发类型:
选择云端外部接入的模式:
应用开发,是指按照平台对SaaS了应用的对接要求,要求应用提供指定的接口,或者平台为应用提供必要的接口。目前,平台要求应用提供的接口有3个(生产租户接口、回收租户接口、免密登录接口)。
另外,为了实现对接应用与平台之间的账户互认,平台提供了获取当前用户(包括子账户)的手机号。
用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用或者直接访问SaaS提供的公网域名来访问应用,当用户购买的应用到期后系统会自动发起SaaS租户的回收操作。所以您需要实现CreateInstance接口,来实现用户购买应用场景。
默认接口超时时间为5秒。
Protocol | HTTPS |
---|---|
Method | POST |
参数 | 类型 | 必填 | 描述 |
---|---|---|---|
id | String | 是 | 该次访问唯一标示符(幂等验证ID) |
tenantId | String | 是 | IoT平台标识一个租户的唯一ID |
appId | String | 是 | 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同 |
appType | String | 是 | “TRYOUT”:试用,用户可以有一个短暂的试用期,该功能在商品上架时ISV可以选择是否提供试用逻辑 “PRODUCTION”:正式购买,用户正式下单购买 |
moduleAttribute | String | 否 | JSON字符串,主要包含上架时候配置的额外计费项参数列表。JSON转化成Map 详见下方 参数介绍【moduleAttribute】 |
参数 | 类型 | 描述 |
---|---|---|
code | Integer | 调用成功返回200;若调用失败返回203; |
message | String | code返回203时填写具体失败原因,code返回200填写success |
userId | String | SaaS标识一个租户的唯一ID,对应的appid不同返回的userid也不同 |
注:同一个用户购买同一款SaaS软件,会多次请求该接口,请求参数中appId不同,saas应用需要返回不同的userId
用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用或者直接访问SaaS提供的公网域名来访问应用,当用户购买的应用到期后系统会自动发起SaaS租户的回收操作。所以您需要实现DeleteInstance接口,来实现用户购买应用到期回收场景;
默认接口超时时间为5秒;
Protocol | HTTPS |
---|---|
Method | POST |
参数 | 类型 | 必填 | 描述 |
---|---|---|---|
id | String | 是 | 该次访问唯一标示符(幂等验证ID) |
tenantId | String | 是 | IoT平台标识一个租户的唯一ID |
userId | String | 是 | SaaS标识一个租户的唯一ID |
appId | String | 是 | 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同 |
参数 | 类型 | 描述 |
---|---|---|
code | Integer | 调用成功返回200;若调用失败返回203; |
message | String | code返回203时填写具体失败原因,code返回200填写success |
参见【验签说明】
用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用来访问应用,这时用户无需再次输入SaaS的账户/密码即可进入SaaS系统进行业务操作,所以您需要实现GetSSOUrl接口,来实现用户访问应用时的免登场景;
SaaS返回的SSOUrl必须满足以下两种安全策略中的一种
默认接口超时时间为5秒。
注: SaaS生成的token信息需要带有验证用户身份及时限性,生成和验证的过程全部由SaaS完成,IoT只做免登URL的透传;
Protocol | HTTPS |
---|---|
Method | POST |
参数 | 类型 | 必填 | 描述 |
---|---|---|---|
id | String | 是 | 该次访问唯一标示符(幂等验证ID) |
tenantId | String | 是 | IoT平台标识一个租户的唯一ID |
tenantSubUserId | String | 否 | IoT平台租户组织架构中的员工唯一ID,当员工账号免登时填写 |
appId | String | 是 | 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同 |
userId | String | 是 | SaaS标识一个租户的唯一ID |
参数 | 类型 | 描述 |
---|---|---|
code | Integer | 调用成功返回200;若调用失败返回203; |
message | String | code返回203时填写具体失败原因,code返回200填写success |
ssoUrl | String | 免登url,需要带有验证用户身份及时限性的token信息 |
参见【验签说明】
{
"code":200,
"message":"success",
"ssoUrl":"https://www.test123.com/login.html?ssoToken=xdasfdasdfasfdasfdaf&checkToken=ddddddd"}
}
SaaS服务如果需要租户或者组织架构中员工手机号,可以通过该接口获取;
授权过程: IoT平台授权appkey调用该接口
权限展示:该权限属于用户隐私数据,所以用户在市场购买时会提示用户当前应用会读取他的手机,并且SaaS不可泄露用户手机号信息;
处于安全考虑,单用户手机号只允许获取一次,如果有业务需求请获取后存储于您的系统中,并在相关性协议条款允许范围内使用该信息;
protocol | httpMethod | apiVer | url | host |
---|---|---|---|---|
HTTPS | POST | 1.0.0 | /app/user/info/get | api.link.aliyun.com |
参数 | 类型 | 必填 | 描述 |
---|---|---|---|
tenantId | String | 是 | IoT平台标识一个租户的唯一ID |
appId | String | 是 | 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同 |
tenantSubUserId | String | 否 | IoT平台租户组织架构中的员工唯一ID,当员工账号免登时填写。 |
userId | String | 是 | SaaS返回的租户唯一ID |
参数 | 类型 | 描述 |
---|---|---|
id | String | IoT平台返回的调用唯一ID |
code | Integer | 调用成功返回200;若调用失败返回错误码; |
message | String | code返回非200时填写具体失败原因,code返回200填写success |
data | “data”:{ “phone”:”13000000000” } |
当tenantSubUserId未填写时返回租户tenantId对应手机号,当tenantSubUserId填写时返回租户下组织架构中tenantSubUserId的手机号 |
参见【验签说明】
{
"id":"dj182318jjkhsljkdhfjahjdhfla"
"code":200,
"message":"success",
"data":{
"phone":"13000000000"
}
}
错误码列表
错误码 | 错误信息 | 描述 |
---|---|---|
200 | success | 成功 |
400 | request error | 请求错误 |
401 | request auth error | 请求认证错 |
403 | request forbidden | 请求被禁止 |
404 | service not found | 服务未找到 |
429 | too many requests | 太多请求 |
460 | request parameter error | 请求参数错误 |
500 | service error | 服务端错误 |
503 | service not available | 服务不可用 |
您可以直接下载签名的多语言demo,您可以在demo代码中找到加签的逻辑,如果一下列表不包含您的开发语言,您也可以通过阅读【加签说明】来自行开发加签逻辑;
开发语言 | sdk demo地址 |
---|---|
java | https://github.com/aliyun/iotx-api-gateway-client |
python | https://github.com/aliyun/api-gateway-demo-sign-python |
php | https://github.com/aliyun/api-gateway-demo-sign-php |
c# | https://github.com/aliyun/api-gateway-demo-sign-net |
android | https://github.com/aliyun/api-gateway-demo-sign-android |
以下将为您介绍如何通过AppKey&AppSecret来对HTTP/HTTPS请求做加签,验签的过程需要您按照以下加签过程重新加签,如果您计算的签名和请求的签名一致即可认为验签成功;
HTTPMethod 为全大写,如 POST。
String stringToSign=
HTTPMethod + "\n" +
Accept + "\n" + //建议显示设置 Accept Header。当 Accept 为空时,部分 Http 客户端会给 Accept 设置默认值为 /,导致签名校验失败。
Content-MD5 + "\n"
Content-Type + "\n" +
Date + "\n" +
Headers +
Url
1.1. Content-MD5
Content-MD5 是指 Body 的 MD5 值,只有当 Body 非 Form 表单时才计算 MD5,计算方式为:
// bodyStream 为字节数组。
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
1.2. Headers
Headers 是指参与 Headers 签名计算的 Header 的 Key、Value 拼接的字符串,建议对 X-Ca 开头以及自定义Header 计算签名,注意如下参数不参与 Headers 签名计算:X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date。
Headers 组织方法: 先对参与 Headers 签名计算的 Header的Key 按照字典排序后使用如下方式拼接,如果某个 Header 的 Value 为空,则使用 HeaderKey + “:” + “\n”参与签名,需要保留 Key 和英文冒号。
String headers =
HeaderKey1 + ":" + HeaderValue1 + "\n"+
HeaderKey2 + ":" + HeaderValue2 + "\n"+
...
HeaderKeyN + ":" + HeaderValueN + "\n"
将 Headers 签名中 Header 的 Key 使用英文逗号分割放到 Request 的 Header 中,Key为:X-Ca-Signature-Headers。
1.3. Url
Url 指 Path + Query + Body 中 Form 参数,组织方法:对 Query+Form 参数按照字典对 Key 进行排序后按照如下方法拼接,如果 Query 或 Form 参数为空,则 Url = Path,不需要添加 ?,如果某个参数的 Value 为空只保留 Key 参与签名,等号不需要再加入签名。
String url =
Path +
"?" +
Key1 + "=" + Value1 +
"&" + Key2 + "=" + Value2 +
...
"&" + KeyN + "=" + ValueN
注意这里Query或Form参数的Value可能有多个,多个的时候只取第一个 Value 参与签名计算。
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
// secret为AppSecret
byte[] keyBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
String sign = new String(Base64.encodeBase64(hmacSha256.doFinal(stringToSign.getBytes("UTF-8")),"UTF-8"));
3. 传递签名
将计算的签名结果放到 Request 的 Header 中,Key为:X-Ca-Signature。
4. 签名排查
当签名校验失败时,API网关会将服务端的 StringToSign 放到 HTTP Response 的 Header 中返回到客户端,Key为:X-Ca-Error-Message,只需要将本地计算的 StringToSign 与服务端返回的 StringToSign 进行对比即可找到问题;
如果服务端与客户端的 StringToSign 一致请检查用于签名计算的密钥是否正确;因为 HTTP Header 中无法表示换行,因此 StringToSign 中的换行符都被过滤掉了,对比时请忽略换行符。
private ApiRequest toApiRequest(HttpServletRequest request){
ApiRequest apiRequest = new ApiRequest(HttpMethod.POST_FORM, request.getRequestURI());
//验签
String signHeaders = request.getHeader("X-Ca-Signature-Headers");
List<String> signHeaderList = new ArrayList<>(16);
if (StringUtils.isNotBlank(signHeaders)) {
signHeaderList = Arrays.asList(signHeaders.split(","));
for (String headerName : signHeaderList) {
if (StringUtils.isNotBlank(headerName)) {
apiRequest.addHeader(headerName, request.getHeader(headerName));
}
}
}
apiRequest.addHeader("Accept", request.getHeader("Accept"));
apiRequest.addHeader("Content-MD5", request.getHeader("Content-MD5"));
apiRequest.addHeader("Content-Type", request.getHeader("Content-Type"));
apiRequest.addHeader("Date", request.getHeader("Date"));
Map<String, String> queryParams = new HashMap<>(8);
Map<String, String[]> requestParams = request.getParameterMap();
if (requestParams.size() > 0) {
for (Map.Entry<String, String[]> param : requestParams.entrySet()) {
queryParams.put(param.getKey(), param.getValue().length > 0 ? param.getValue()[0] : "");
apiRequest.addParam(param.getKey(), param.getValue().length > 0 ? param.getValue()[0] : "",
ParamPosition.QUERY, true);
}
}
return apiRequest;
}
选择版本管理下的“模型与权限”,如果您需要访问租户的手机号等信息,则需要开通对应服务
在文档使用中是否遇到以下问题
更多建议
匿名提交