SaaS應用對接
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”:”1300000****” } | 當tenantSubUserId未填寫時返回租戶tenantId對應手機號,當tenantSubUserId填寫時返回租戶下組織架構中tenantSubUserId的手機號 |
加簽說明
參見【驗簽說明】
返回示例
{ ? "?id":"dj182318jjkhsljkdhfjahjdhfla" "code":200, "message":"success", "data":{ "phone":"1300000****" } }
錯誤碼列表
錯誤碼 | 錯誤信息 | 描述 |
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.gatewaysdk-core-java1.1.0 |
python | |
php | |
c# | |
android |
驗簽
以下將為您介紹如何通過AppKey&AppSecret來對HTTP/HTTPS請求做加簽,驗簽的過程需要您按照以下加簽過程重新加簽,如果您計算的簽名和請求的簽名一致即可認為驗簽成功。
簽名內容
String stringToSign= 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-MD5Content-MD5 是指 Body 的 MD5 值,只有當 Body 非 Form 表單時才計算 MD5,計算方式為:
// bodyStream 為字節數組。
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
1.2. HeadersHeaders 是指參與 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. UrlUrl 指 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 參與簽名計算。
2. 計算簽名
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 中的換行符都被過濾掉了,對比時請忽略換行符。
5. Java驗證示例
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;
}
第四步:接口授權
選擇版本管理下的“模型與權限”,如果您需要訪問租戶的手機號等信息,則需要開通對應服務。