全部产品
云市场

SaaS应用对接

更新时间:2019-04-29 11:04:21

SaaS应用介绍

在应用托管体系中,并未所有应用的分发模式都是按照Kubernetes方式,对每一次应用向最终用户的交付形态,都是交付一个真实的物理运行空间。还有这里要提到的SaaS类应用。此类应用,其分发模式,仅仅是在原有系统中,分配了一个账户,交付给客户。因此,为了兼容单租户模式的独立托管部署和这类多租户的SaaS系统,平台抽象了与这类SaaS系统的交互行为,应用为满足这个交互行为,需要完成本文所提的对接要求。

整体链路

image.png

功能边界

租户管理

image.png

IoT平台管理阿里云租户与应用内租户的映射关系(支持一个阿里云租户拥有同一个应用的多个租户),SaaS管理应用内部的租户结构

组织架构

image.png
本期无需对接IoT平台的组织架构体系,SaaS内部自行管理组织架构,但是两边的组织架构需要能够定位到同一个员工身份, 目前通过员工电话作为识别的唯一标识;

权限管理

image.png
IoT平台管理租户及其组织架构中的员工是否有权限访问应用,SaaS管理租户及组织架构中员工访问功能列表;

对接开发

第一步:AppKey的获取

托管应用自动产生

应用部署在应用托管平台中,系统会为部署的每个应用颁发AppKey,AppKey通过环境变量方式注入至业务代码运行的容器中,供代码获取并使用;

人工创建

这种场景适用于您自行通过外置服务器的方式来搭建SaaS服务的模式。创建AppKey的过程如下问:
登录物联网平台,在“应用托管”->”AppKey”页面,点击“创建AppKey按钮”:
image.png

  • 点击创建AppKey按钮,打开新建AppKey页面,填写以下信息:
    • 应用名称: 标识appkey使用的应用的描述;
    • 应用类型: 如果您的设备是通过阿里云物联网平台接入,则勾选;
  • 点击对话框上的“创建AppKey”按钮:

image.png

第二步:应用开发

应用开发,是指按照平台对SaaS了应用的对接要求,要求应用提供指定的接口,或者平台为应用提供必要的接口。目前,平台要求应用提供的接口有3个(生产租户接口、回收租户接口、免密登录接口)。
另外,为了实现对接应用与平台之间的账户互认,平台提供了获取当前用户(包括子账户)的手机号。

接口开发1: 生产租户

场景描述

用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用或者直接访问SaaS提供的公网域名来访问应用,当用户购买的应用到期后系统会自动发起SaaS租户的回收操作。所以您需要实现CreateInstance接口,来实现用户购买应用场景。

超时时间

默认接口超时时间为5秒。

调用时序

image.png

接口说明

  1. CreateInstance生产服务
Protocol HTTPS
Method POST
  1. 请求参数
参数 类型 必填 描述
id String 该次访问唯一标示符(幂等验证ID)
tenantId String IoT平台标识一个租户的唯一ID
appId String 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同
appType String “TRYOUT”:试用,用户可以有一个短暂的试用期,该功能在商品上架时ISV可以选择是否提供试用逻辑
“PRODUCTION”:正式购买,用户正式下单购买
moduleAttribute String JSON字符串,主要包含上架时候配置的额外计费项参数列表。JSON转化成Map之后,示例:{“key1”:”rjmjz2”,”key2”:”2”,”key3”:”rjmjz2”}
详见下方 参数介绍【moduleAttribute】
  1. 返回参数
参数 类型 描述
code Integer 调用成功返回200;若调用失败返回203;
message String code返回203时填写具体失败原因,code返回200填写success
userId String SaaS标识一个租户的唯一ID,对应的appid不同返回的userid也不同

注:同一个用户购买同一款SaaS软件,会多次请求该接口,请求参数中appId不同,saas应用需要返回不同的userId

  1. 验签说明
    参见【验签说明】

  2. 返回示例

    1. {
    2. "code":200,
    3. "message":"success",
    4. "userId":"my saas user id"
    5. }
  1. 参数介绍【moduleAttribute】
  • step1: 卖家在进行商品上架时指定额外计费项,当前示例中指定该收费项参数为”service_door”

image.png

  • step2: 买家在市场下单购买商品时输入的额外计费项,当前示例中输入购买数量为200

image.png

  • step3:买家下单后iot平台调用生产租户接口时传递的moduleAttribute参数值为{“service_door”:”200”}

接口开发2: 回收租户

场景描述

用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用或者直接访问SaaS提供的公网域名来访问应用,当用户购买的应用到期后系统会自动发起SaaS租户的回收操作。所以您需要实现DeleteInstance接口,来实现用户购买应用到期回收场景;

超时时间

默认接口超时时间为5秒;

调用时序

image.png

接口说明

  1. DeleteInstance注销服务
Protocol HTTPS
Method POST
  1. 请求参数
参数 类型 必填 描述
id String 该次访问唯一标示符(幂等验证ID)
tenantId String IoT平台标识一个租户的唯一ID
userId String SaaS标识一个租户的唯一ID
appId String 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同
  1. 返回参数
参数 类型 描述
code Integer 调用成功返回200;若调用失败返回203;
message String code返回203时填写具体失败原因,code返回200填写success
  1. 验签说明

参见【验签说明】

  1. 返回示例
    1. {
    2. "code":200,
    3. "message":"success"
    4. }

接口开发3: 免密登录

场景描述

用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用来访问应用,这时用户无需再次输入SaaS的账户/密码即可进入SaaS系统进行业务操作,所以您需要实现GetSSOUrl接口,来实现用户访问应用时的免登场景;

安全说明

SaaS返回的SSOUrl必须满足以下两种安全策略中的一种

  • 每次生成的Url只允许**一次使用**
  • 限制临时token的有效时间范围(推荐30秒)

超时时间

默认接口超时时间为5秒。

调用时序

image.png
注: SaaS生成的token信息需要带有验证用户身份及时限性,生成和验证的过程全部由SaaS完成,IoT只做免登URL的透传;

接口说明

  1. GetSSOUrl生产服务
Protocol HTTPS
Method POST
  1. 请求参数
参数 类型 必填 描述
id String 该次访问唯一标示符(幂等验证ID)
tenantId String IoT平台标识一个租户的唯一ID
tenantSubUserId String IoT平台租户组织架构中的员工唯一ID,当员工账号免登时填写
appId String 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同
userId String SaaS标识一个租户的唯一ID
  1. 返回参数
参数 类型 描述
code Integer 调用成功返回200;若调用失败返回203;
message String code返回203时填写具体失败原因,code返回200填写success
ssoUrl String 免登url,需要带有验证用户身份及时限性的token信息
  1. 验签说明

参见【验签说明】

  1. 返回示例
    1. {
    2. "code":200,
    3. "message":"success",
    4. "ssoUrl":"https://www.test123.com/login.html?ssoToken=xdasfdasdfasfdasfdaf&checkToken=ddddddd"}
    5. }

接口提供1: 手机获取

场景描述

SaaS服务如果需要租户或者组织架构中员工手机号,可以通过该接口获取;
授权过程: IoT平台授权appkey调用该接口
权限展示:该权限属于用户隐私数据,所以用户在市场购买时会提示用户当前应用会读取他的手机,并且SaaS不可泄露用户手机号信息;

安全说明

处于安全考虑,单用户手机号只允许获取一次,如果有业务需求请获取后存储于您的系统中,并在相关性协议条款允许范围内使用该信息;

调用时序

image.png

接口说明

  1. GetUserPhone手机号获取服务
protocol httpMethod apiVer url host
HTTPS POST 1.0.0 /app/user/info/get api.link.aliyun.com
  1. 请求参数
参数 类型 必填 描述
tenantId String IoT平台标识一个租户的唯一ID
appId String 应用唯一ID,一个租户可以重复购买一款软件,每次购买appId都不同
tenantSubUserId String IoT平台租户组织架构中的员工唯一ID,当员工账号免登时填写。
userId String SaaS返回的租户唯一ID
  1. 返回参数
参数 类型 描述
id String IoT平台返回的调用唯一ID
code Integer 调用成功返回200;若调用失败返回错误码;
message String code返回非200时填写具体失败原因,code返回200填写success
data “data”:{
“phone”:”13000000000”
}
当tenantSubUserId未填写时返回租户tenantId对应手机号,当tenantSubUserId填写时返回租户下组织架构中tenantSubUserId的手机号
  1. 加签说明

参见【验签说明】

  1. 返回示例

    1. {
    2. "​id":"dj182318jjkhsljkdhfjahjdhfla"
    3. "code":200,
    4. "message":"success",
    5. "data":{
    6. "phone":"13000000000"
    7. }
    8. }
  2. 错误码列表

错误码 错误信息 描述
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

com.aliyun.api.gateway
sdk-core-java
1.1.0
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请求做加签,验签的过程需要您按照以下加签过程重新加签,如果您计算的签名和请求的签名一致即可认为验签成功;

  1. 签名内容
    1. String stringToSign=
    2. HTTPMethod + "\n" +
    3. Accept + "\n" + //建议显示设置 Accept Header。当 Accept 为空时,部分 Http 客户端会给 Accept 设置默认值为 /,导致签名校验失败。
    4. Content-MD5 + "\n"
    5. Content-Type + "\n" +
    6. Date + "\n" +
    7. Headers +
    8. Url
    HTTPMethod 为全大写,如 POST。
    Accept、Content-MD5、Content-Type、Date 如果为空也需要添加换行符”\n”,Headers如果为空不需要添加”\n”。

1.1. Content-MD5
Content-MD5 是指 Body 的 MD5 值,只有当 Body 非 Form 表单时才计算 MD5,计算方式为:

  1. // bodyStream 为字节数组。
  2. 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 和英文冒号。

  1. String headers =
  2. HeaderKey1 + ":" + HeaderValue1 + "\n"+
  3. HeaderKey2 + ":" + HeaderValue2 + "\n"+
  4. ...
  5. 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 参与签名,等号不需要再加入签名。

  1. String url =
  2. Path +
  3. "?" +
  4. Key1 + "=" + Value1 +
  5. "&" + Key2 + "=" + Value2 +
  6. ...
  7. "&" + KeyN + "=" + ValueN

注意这里Query或Form参数的Value可能有多个,多个的时候只取第一个 Value 参与签名计算。

2. 计算签名

  1. Mac hmacSha256 = Mac.getInstance("HmacSHA256");
  2. // secret为AppSecret
  3. byte[] keyBytes = secret.getBytes("UTF-8");
  4. hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
  5. 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 中的换行符都被过滤掉了,对比时请忽略换行符。

5. Java验证示例

  1. private ApiRequest toApiRequest(HttpServletRequest request){
  2. ApiRequest apiRequest = new ApiRequest(HttpMethod.POST_FORM, request.getRequestURI());
  3. //验签
  4. String signHeaders = request.getHeader("X-Ca-Signature-Headers");
  5. List<String> signHeaderList = new ArrayList<>(16);
  6. if (StringUtils.isNotBlank(signHeaders)) {
  7. signHeaderList = Arrays.asList(signHeaders.split(","));
  8. for (String headerName : signHeaderList) {
  9. if (StringUtils.isNotBlank(headerName)) {
  10. apiRequest.addHeader(headerName, request.getHeader(headerName));
  11. }
  12. }
  13. }
  14. apiRequest.addHeader("Accept", request.getHeader("Accept"));
  15. apiRequest.addHeader("Content-MD5", request.getHeader("Content-MD5"));
  16. apiRequest.addHeader("Content-Type", request.getHeader("Content-Type"));
  17. apiRequest.addHeader("Date", request.getHeader("Date"));
  18. Map<String, String> queryParams = new HashMap<>(8);
  19. Map<String, String[]> requestParams = request.getParameterMap();
  20. if (requestParams.size() > 0) {
  21. for (Map.Entry<String, String[]> param : requestParams.entrySet()) {
  22. queryParams.put(param.getKey(), param.getValue().length > 0 ? param.getValue()[0] : "");
  23. apiRequest.addParam(param.getKey(), param.getValue().length > 0 ? param.getValue()[0] : "",
  24. ParamPosition.QUERY, true);
  25. }
  26. }
  27. return apiRequest;
  28. }

第三步:服务注册

在应用开发完成指定的对接接口之后,需要将应用实现的接口信息注册在平台上,以便平台按需调用,这个过程我们称之为“服务注册”。
在创建AppKey页面中点击“立即配置”或者列表操作项中点击“配置”进入AppKey配置页面。
image.png

选择AppKey配置下的“服务注册”tab,将需要的注册的服务地址填写至平台,并点击“注册”按钮,即可相关接口绑定至IoT网关;
image.png
填写内容说明:
服务域名: saas服务暴露的域名,必填。
协议: HTTP/HTTPS,必填,http协议可以在测试环境中使用,如果需要将软件发布到市场则需要使用https
* 生产租户URI/注销租户URI/免密登录URI: 三个接口的资源目录,必填。
在AppKey创建并配置后,您可以在服务调试中逐步调试接口;
image.png

第四步:接口授权

选择AppKey配置下的“接口授权”tab,如果您需要访问租户的手机号等信息,则需要开通对应服务
image.png