SaaS应用介绍
在应用托管体系中,并未所有应用的分发模式都是按照Kubernetes方式,对每一次应用向最终用户的交付形态,都是交付一个真实的物理运行空间。还有这里要提到的SaaS类应用。此类应用,其分发模式,仅仅是在原有系统中,分配了一个账户,交付给客户。因此,为了兼容单租户模式的独立托管部署和这类多租户的SaaS系统,平台抽象了与这类SaaS系统的交互行为,应用为满足这个交互行为,需要完成本文所提的对接要求。
整体链路
功能边界
租户管理
IoT平台管理阿里云租户与应用内租户的映射关系(支持一个阿里云租户拥有同一个应用的多个租户),SaaS管理应用内部的租户结构
组织架构
本期无需对接IoT平台的组织架构体系,SaaS内部自行管理组织架构,但是两边的组织架构需要能够定位到同一个员工身份, 目前通过员工电话作为识别的唯一标识;
权限管理
IoT平台管理租户及其组织架构中的员工是否有权限访问应用,SaaS管理租户及组织架构中员工访问功能列表;
对接开发
第一步:AppKey的获取
托管应用自动产生
应用部署在应用托管平台中,系统会为部署的每个应用颁发AppKey,AppKey通过环境变量方式注入至业务代码运行的容器中,供代码获取并使用;
人工创建
这种场景适用于您自行通过外置服务器的方式来搭建SaaS服务的模式。创建AppKey的过程如下问:
登录卖家中心,在“应用接入”->”应用管理”->“创建应用”账号分发类型:
选择云端外部接入的模式:
第二步:应用开发
应用开发,是指按照平台对SaaS了应用的对接要求,要求应用提供指定的接口,或者平台为应用提供必要的接口。目前,平台要求应用提供的接口有3个(生产租户接口、回收租户接口、免密登录接口)。
另外,为了实现对接应用与平台之间的账户互认,平台提供了获取当前用户(包括子账户)的手机号。
接口开发1: 生产租户
场景描述
用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用或者直接访问SaaS提供的公网域名来访问应用,当用户购买的应用到期后系统会自动发起SaaS租户的回收操作。所以您需要实现CreateInstance接口,来实现用户购买应用场景。
超时时间
默认接口超时时间为5秒。
调用时序
接口说明CreateInstance生产服务
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之后,示例:{“key1”:”rjmjz2”,”key2”:”2”,”key3”:”rjmjz2”}
详见下方 参数介绍【moduleAttribute】
返回参数
参数
类型
描述
code
Integer
调用成功返回200;若调用失败返回203;
message
String
code返回203时填写具体失败原因,code返回200填写success
userId
String
SaaS标识一个租户的唯一ID,对应的appid不同返回的userid也不同
注:同一个用户购买同一款SaaS软件,会多次请求该接口,请求参数中appId不同,saas应用需要返回不同的userId
验签说明
参见【验签说明】
返回示例
{
"code":200,
"message":"success",
"userId":"my saas user id"
}
参数介绍【moduleAttribute】
step1: 卖家在进行商品上架时指定额外计费项,当前示例中指定该收费项参数为”service_door”
step2: 买家在市场下单购买商品时输入的额外计费项,当前示例中输入购买数量为200
step3:买家下单后iot平台调用生产租户接口时传递的moduleAttribute参数值为{“service_door”:”200”}
接口开发2: 回收租户
场景描述
用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用或者直接访问SaaS提供的公网域名来访问应用,当用户购买的应用到期后系统会自动发起SaaS租户的回收操作。所以您需要实现DeleteInstance接口,来实现用户购买应用到期回收场景;
超时时间
默认接口超时时间为5秒;
调用时序
接口说明DeleteInstance注销服务
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
验签说明
参见【验签说明】
返回示例{
"code":200,
"message":"success"
}
接口开发3: 免密登录
场景描述
用户(阿里云账户体系)在物联网市场下单购买应用后需要通知SaaS为当前用户开立一个可访问SaaS服务的租户(账户),用户可以通过点击物联网市场中已购买的应用来访问应用,这时用户无需再次输入SaaS的账户/密码即可进入SaaS系统进行业务操作,所以您需要实现GetSSOUrl接口,来实现用户访问应用时的免登场景;
安全说明
SaaS返回的SSOUrl必须满足以下两种安全策略中的一种
每次生成的Url只允许**一次使用**
限制临时token的有效时间范围(推荐30秒)
超时时间
默认接口超时时间为5秒。
调用时序
注: SaaS生成的token信息需要带有验证用户身份及时限性,生成和验证的过程全部由SaaS完成,IoT只做免登URL的透传;
接口说明GetSSOUrl生产服务
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"}
}
接口提供1: 手机获取
场景描述
SaaS服务如果需要租户或者组织架构中员工手机号,可以通过该接口获取;
授权过程: IoT平台授权appkey调用该接口
权限展示:该权限属于用户隐私数据,所以用户在市场购买时会提示用户当前应用会读取他的手机,并且SaaS不可泄露用户手机号信息;
安全说明
处于安全考虑,单用户手机号只允许获取一次,如果有业务需求请获取后存储于您的系统中,并在相关性协议条款允许范围内使用该信息;
调用时序
接口说明GetUserPhone手机号获取服务
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代码中找到加签的逻辑,如果一下列表不包含您的开发语言,您也可以通过阅读【加签说明】来自行开发加签逻辑;
验签
以下将为您介绍如何通过AppKey&AppSecret来对HTTP/HTTPS请求做加签,验签的过程需要您按照以下加签过程重新加签,如果您计算的签名和请求的签名一致即可认为验签成功;
签名内容StringstringToSign=
HTTPMethod+"\n"+
Accept+"\n"+//建议显示设置 Accept Header。当 Accept 为空时,部分 Http 客户端会给 Accept 设置默认值为 /,导致签名校验失败。
Content-MD5+"\n"
Content-Type+"\n"+
Date+"\n"+
Headers+
Url
HTTPMethod 为全大写,如 POST。
Accept、Content-MD5、Content-Type、Date 如果为空也需要添加换行符”\n”,Headers如果为空不需要添加”\n”。
1.1. Content-MD5
Content-MD5 是指 Body 的 MD5 值,只有当 Body 非 Form 表单时才计算 MD5,计算方式为:
// bodyStream 为字节数组。
Stringcontent-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 和英文冒号。
Stringheaders=
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 参与签名,等号不需要再加入签名。
Stringurl=
Path+
"?"+
Key1+"="+Value1+
"&"+Key2+"="+Value2+
...
"&"+KeyN+"="+ValueN
注意这里Query或Form参数的Value可能有多个,多个的时候只取第一个 Value 参与签名计算。
2. 计算签名
MachmacSha256=Mac.getInstance("HmacSHA256");
// secret为AppSecret
byte[]keyBytes=secret.getBytes("UTF-8");
hmacSha256.init(newSecretKeySpec(keyBytes,0,keyBytes.length,"HmacSHA256"));
Stringsign=newString(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验证示例
privateApiRequesttoApiRequest(HttpServletRequestrequest){
ApiRequestapiRequest=newApiRequest(HttpMethod.POST_FORM,request.getRequestURI());
//验签
StringsignHeaders=request.getHeader("X-Ca-Signature-Headers");
ListsignHeaderList=newArrayList<>(16);
if(StringUtils.isNotBlank(signHeaders)){
signHeaderList=Arrays.asList(signHeaders.split(","));
for(StringheaderName: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"));
MapqueryParams=newHashMap<>(8);
MaprequestParams=request.getParameterMap();
if(requestParams.size()>0){
for(Map.Entryparam: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);
}
}
returnapiRequest;
}
第四步:接口授权
选择版本管理下的“模型与权限”,如果您需要访问租户的手机号等信息,则需要开通对应服务