自定义门户¶
最后更新:2022-11-15
政企通常希望有自己的个性门户, 除了在控制台的“个性定制”能修改LOGO和名称外, 同时, 还希望通过代码, 能进一步集成到自己深度定制开发的门户中。 本文档覆盖了后者需要调用的API接口及能力。
1. 术语定义¶
参考:IDP中产品术语介绍
2. 什么是自定义门户¶
门户一般指企业员工统一进入业务系统的入口系统网站,简称portal。门户一般带有企业自主风格的统一登录界面,相对于使用IDaaS提供的统一登录页,一般都有改造登录页的需求。
注意:这并不意味着自定义门户登录界面就一定都是自己做,也有少数需要使用IDaaS统一登录页的情况。
3. 自定义门户和业务系统接入IDaaS区别¶
业务系统,即SP, 一般仅会接入IDaaS的单点登录,和退出功能。
自定义门户portal则不同,除了接入IDaaS单点登录(可选)和退出,还需要获取用户在IDaaS平台上授权的应用单点登录地址,以支持在自己的门户网站上展示。而获取用户在IDaaS平台上授权的应用列表,则必须使用用户的访问令牌access_token
业务系统和IDaaS对接单点登录时,需要对接用户的身份令牌(id_token)。 门户和IDaaS对接时,除了对接用户的身份令牌,主要需要获取到用户的访问令牌(access_token),凭此来调用IDaaS用户相关的API。
4. 场景描述¶
企业的门户接入IDaaS登录,退出,以及获取用户授权的应用列表接口能力,实现企业用户从门户登录后,能直接单点登录到各个业务系统。当用户从门户退出时,退出IDaaS相关的用户会话,(可能伴随以及其他已经登录的业务系统),又重新回到门户的登录页。
5. 接入流程¶
接入顺序主要为:
创建OAuth2应用 -> 对接IDaaS登录 -> 调用IDaaS获取用户授权的应用接口 > 对接IDaaS退出 > 测试
对于门户来说,对接IDaaS登录可分为:
方式一:直接调用IDaaS登录接口
方式二:使用IDaaS统一登录页两种情况(推荐)
方式三:无密码,使用API获取用户token
以下为门户接入IDaaS时使用IDaaS的登录接口的完整时序图
5.1. 创建OAuth2的门户应用¶
使用管理员登录IDaaS平台,在添加应用,标准协议中选择OAuth2模板,点击添加, 比如XX门户, 如下图
Redirect URI:授权码模式下,门户需要进行接收IDaaS产生的临时code地址,凭借临时code,门户可再次通过授权码得到用户的访问令牌access_token
GrantType: 此处选择 authorization_code
5.2. 对接IDaaS登录¶
门户接入IDaaS登录时,可分为两种情况:
方式一(推荐):办公门户使用IDaaS的统一登录页。用户登录时直接引导到IDaaS的统一登录页,后续门户获取IDaaS用户访问令牌access_token
方式二:办公门户使用自己的登录页,用户登录时调用IDaaS的接口进行登录,IDaaS返回用户的访问令牌access_token
方式三:大屏等门户使用IDaaS的登录接口,但提供的是应用的AK,而不是用户的密码。 通过此种方式可以跳过access_token, 直接得到用户的应用列表。
注意:无论选择哪种,其最终目的都是为了获取到用户的访问令牌access_token
5.2.1. 方式一:使用IDaaS统一登录页登录¶
使用IDaaS统一登录页登录时,其流程为标准的Oauth2授权码模式获取用户访问令牌access_token。
注意调用此接口,使用的client_id和client_secret为步骤1创建OAuth2应用的client_id和client_secret,在查看详情中展示的client_id,client_secret
详细获取流程如下
第一步: 构造Authorize URL,获取用户授权code
门户可通过一个按钮或其他方式,触发浏览器打开Authorize URL,用户登录成功后,会跳转到回调地址Redirect URI,并把code参数一同转发过去。
Authorize URL 格式为
http(s)://{IDaaS_server}/oauth/authorize?response_type=code&scope=read&client_id={client_id}&redirect_uri={redirect_uri}&state={state}
参数名称 |
参数类型 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
response_type |
text |
是 |
code |
响应类型,固定 |
scope |
text |
是 |
read |
授权范围,固定read |
client_id |
text |
是 |
1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U |
OAuth2应用详情Client Id |
redirect_uri |
text |
是 |
http%3A%2F%2Foa.com%2Fcallback |
OAuth2应用详情 |
state |
text |
是 |
10ff0be64971c07f893afc332877f68arS8FH2iyZni_idp |
随机值,若为门户发起,可自定义随机一个字符串,若为IDP发起,也是随机一个值,但会带上_idp后缀 |
补充说明
浏览器访问完整地址示例如下
http://{IDaaS_server}/oauth/authorize?response_type=code&scope=read&client_id=1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U&redirect_uri=http%3A%2F%2Foa.com%2Fcallback&state=10ff0be64971c07f893afc332877f68arS8FH2iyZni_idp
访问完成后,会跳转至IDaaS登录页。用户登录成功后,会跳转到回调地址Redirect URI,并把code参数一同转发过去。
第二步:code 换取 access_token
门户接收到code后,可凭此code换取用户的访问令牌access_token
详细地址如下:
http(s)://{IDaaS_server}/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}
请求方法为POST
参数名称 |
参数类型 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
grant_type |
text |
是 |
authorization_code |
响应类型,固定 |
code |
text |
是 |
WgWQe6 |
为第一步用户登录成功后IDaaS回调至Redirect URI上请求参数code值 |
client_id |
text |
是 |
1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U |
OAuth2应用详情 |
client_secret |
text |
是 |
vdDtBPNiHRCz8S267fURHYnr2bMlgL7ahqrpgrDscG |
OAuth2应用详情 |
redirect_uri |
text |
是 |
http%3A%2F%2Foa.com%2Fcallback |
OAuth2应用详情 |
补充说明
curl请求示例:
curl -X POST 'http://{IDaaS_server}/oauth/token?grant_type=authorization_code&code=dIKvfA&client_id=1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U&client_secret=vdDtBPNiHRCz8S267fURHYnr2bMlgL7ahqrpgrDscG&redirect_uri=http%3A%2F%2Foa.com%2Fcallback'
成功时响应
{
"access_token":"eyJhbGciO...",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1...",
"expires_in":7199,
"scope":"read",
"jti":"17147278-7f3e-45f2-be6f-8105c4334a30"
}
失败时响应:
client_id或client_secret错误
{
"error":"invalid_client",
"error_description":"Bad client credentials"
}
code错误
{
"error":"invalid_grant",
"error_description":"Invalid authorization code: dIKvfA"
}
code失效
{
"error":"invalid_grant",
"error_description":"authorization code expired: WgWQe6"
}
5.2.2. 方式二:调用IDaaS的接口进行登录¶
当使用门户有自己的登录页,建议使用此种方式。
其流程为标准的OAuth2密码模式(password)获取用户访问令牌access_token。
注意调用此接口,使用的client_id和client_secret为步骤1创建OAuth2应用的API Key和API Secret,在详情API处复制
请求地址: /oauth/token
请求方法: POST
接口描述:第三方/门户系统调用IDAAS登录接口,进行认证用户,IDAAS返回认证结果,若成功,返回用户的access_token,(access_token为调用IDAAS用户相关API的接口凭证)失败时将返回对应错误。
请求参数
Headers
参数名称 |
参数值 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
Content-Type |
application/x-www-form-urlencoded |
是 |
Body
参数名称 |
参数类型 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
client_id |
text |
是 |
0d855656533d0e6988c8ea35c0a52phKHIFq55p |
应用APIKey |
client_secret |
text |
是 |
KWsRAi675ZSYbycbMg7ofmo61ov8hQst6kOi8F |
应用APISecret |
grant_type |
text |
是 |
password |
授权类型,固定password |
scope |
text |
是 |
read |
授权范围,固定read |
username |
text |
是 |
zhangsan |
用户名 |
password |
text |
是 |
123456 |
密码(明文) |
返回数据
名称 |
类型 |
是否必须 |
默认值 |
备注 |
其他信息 |
---|---|---|---|---|---|
access_token |
string |
必须 |
用户令牌 |
||
token_type |
string |
必须 |
令牌类型 |
||
expires_in |
number |
必须 |
有效期,单位s |
||
scope |
string |
必须 |
授权范围 |
||
jti |
string |
非必须 |
若令牌格式为jwt,代表令牌id |
补充说明
curl请求示例:
curl -X POST 'http://{IDaaS_server}/oauth/token?client_id=0d8e9ae61c23533d0e6988c8ea35c0a52phKHIFq55p&client_secret=KWsRAiZhEajM5ZSYbycbMg7ofmo61ov8hQst6kOi8F&grant_type=password&scope=read&username=zhangsan&password=123456' -H 'content-type: application/x-www-form-urlencoded'
认证成功时返回格式如下:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ....",
"token_type": "bearer",
"expires_in": 43199,
"scope": "read",
"jti": "b9c7214b-d900-41c6-9fa0-e76293a1e70d"
}
认证失败时返回格式如下:
client_id或client_secret错误
{
"error": "invalid_client",
"error_description": "Bad client credentials"
}
用户名或密码错误
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
5.2.3. 方式三:使用IDaaS无密码登录¶
在门户为大屏等应用的形势下, 通常会使用应用作为一个整体登录的形式, 这样就不需要提交每个用户的密码了;
使用互信应用插件
适用于无密码登录场景,使用秘钥加密唯一标识,到IDaaS换取用户access_token
支持的算法:SM2、RSA、AES
支持的唯一标识:用户名、手机号、邮箱、外部ID
详细流程如下:
第一步:上传插件(插件请联系客服获取):
第二步:创建应用:
第三步:获取应用密钥:
获取秘钥 无需授权、启用应用、查看应用详细。
创建应用时,会自动生成 SM2密钥对、RSA密钥对、AES秘钥,使用对应秘钥调用认证接口
SM2:使用 idsmanager-sm2 包生成
RSA:使用 idsmanager-oidc 包生成(长度2048)
AES:随机生成(长度32)
第四步:调用接口:
请求地址: /api/public/bff/v1.2/application/plugin_mutualtrust/login
请求方法: POST
接口描述:通过解析加密值得到用户唯一标识,找到用户生成access_token返回。
请求参数
Headers
参数名称 |
参数值 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
Content-Type |
application/json |
是 |
Body
参数名称 |
参数类型 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
algorithmType |
text |
是 |
SM2 |
encryptedIdentity值的加密算法 |
encryptedIdentity |
text |
是 |
1637835035000_zhangsan |
用户唯一标识加密后的值。 |
identityType |
text |
是 |
USERNAME |
用户的身份类型: |
purchaseId |
text |
是 |
xxxx_plugin_mutualtrust |
应用ID |
返回数据
名称 |
类型 |
是否必须 |
默认值 |
备注 |
其他信息 |
---|---|---|---|---|---|
success |
booliean |
必须 |
响应结果 |
||
code |
string |
必须 |
响应码 |
||
message |
string |
必须 |
调用失败时返回错误信息 |
||
data |
Object |
必须 |
有效期,单位s |
||
└access_token |
string |
必须 |
用户的token |
响应码
响应码 |
响应信息 |
备注 |
---|---|---|
200 |
成功 |
|
500 |
系统繁忙 |
请联系管理员 |
400100 |
无效的algorithmType |
根据接口规范修改 |
400101 |
无效的encryptedIdentity |
加密算法或加密内部不正确 |
400102 |
无效的encryptedIdentity,已过期 |
timestamp已超过10分钟或加密服务器与验证服务器时间差大于10分钟 |
400103 |
无效的用户唯一标识 |
用户在系统中未找到 |
400104 |
无效的identityType |
根据接口规范修改 |
400105 |
无效的purchaseId |
需要管理员在应用详细中查看 |
400106 |
无效的purchaseId,应用未启用 |
应用未启用需要管理员在应用管理中启用 |
400107 |
无效的用户唯一标识,用户已禁用 |
账户禁用需要管理员在管理页面启用 |
400108 |
无效的用户唯一标识,用户已锁定 |
账户锁定需要管理员在管理页面解锁 |
补充说明
请求示例:
POST /api/public/bff/v1.2/application/plugin_mutualtrust/login
Host: {Host}
Content-Type: application/json
{
"algorithmType":"SM2",
"encryptedIdentity":"BLTCpPB5wsl/ws2QlJrkijihIqQxg0CQzM2qVdetncw/sM0Edd2kLKi84QM/dqMyKISmTBEP53hvvjbHtR7tLzWvkSrMv2OkCYCs50LkjY4JiVVT9M+/PjvVpBcNij7g714+imp8Pdg=",
"identityType":"USERNAME",
"purchaseId":"zhangdhplugin_mutualtrust2"
}
调用成功响应:
{
"success": true,
"code": "200",
"message": null,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZW50ZXJwcmlzZV9tb2JpbGVfcmVzb3VyY2UiLCJiZmZfYXBpX3Jlc291cmNlIl0sImV4cCI6MTYzNzg2ODI1MCwidXNlcl9uYW1lIjoiemhhbmdkaCIsImp0aSI6IjY4YjBiZmFkLWEzYTItNGRlYi1hZTg4LWYxNGY0YjRjMzM0MSIsImNsaWVudF9pZCI6Ijk2NzRhYjczYjZjOTc4YzE1ZTBiNThjNTY2NmJhNTVlWlJ4SVpqcUNCMzciLCJzY29wZSI6WyJyZWFkIl19.BUznu8_oNGcVcQpMmVNO6bEn7sO11RdzocSS3HrfbYg"
}
}
调用失败响应:
{
"success": true,
"code": "400100",
"message": "无效的algorithmType"
}
第五步:加密代码示例:
SM2方式:
Github demo工程:https://github.com/aliyun-idaas/idp4-developer-sm2-utils
public static void main(String[] args) throws Exception {
//SM2的加密与解密
String publicKey = "BMzrnltiDD/CCfr6r90Rf/qHn8Wc0LqPGm4rkSezjgEOUaxD5eNoBQw4xbdKLIwUj7UpfAtjw6ooH3Duu2qY+Cw=";
String encodeParam = SM2Encrypt.encryptUseBase64(publicKey, System.currentTimeMillis() + "_zhangsan", true);
System.out.println("加密后: " + encodeParam);
}
RSA方式:
package com.idaas.controller;
import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.RsaJsonWebKey;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import java.security.PublicKey;
public class RSADemo {
public static void main(String[] args) throws Exception {
//RSA公钥字符串 注册应用时提供
String publicKeyString = "{\"kty\":\"RSA\",\"kid\":\"2929530392857633439\",\"alg\":\"RS256\",\"n\":\"zhJ0pd63SMsgnoCnk_smt3-ePdjiEjJtveqH2UFLgGomaweA54qpTquYe_XyemoCeegRpwOJFd44NtdJgCMkoIXqVTkrLnEvaK-rSqAkPfsWvwv2QUiicnsb1hpXQv8rOIhQ9Txuae92vp4ZV9XZmf3phTD-hg8YZw1bjkUbyua_veh6oGphAvZoHntFJIrKIyf_oftwGtz9xpiLzbX3saOMq2NoDhUslVT4p2wNN3hVFKRwrCqxOcwTW0sARRAWm-jPJwhQuVa4i01QnHhN4KalHRPX4YVaLOPp57_ajIFOQCd6MFvTGW3i05GdAEbajNQEuYlrugUk-YXOnzDcqQ\",\"e\":\"AQAB\"}";
PublicKey publicKey = new RsaJsonWebKey(JsonUtil.parseJson(publicKeyString)).getPublicKey();
//身份证号
String usernameBefore = System.currentTimeMillis() + "_zhangsan";
//加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(usernameBefore.getBytes("utf-8"));
//转为base64(jdk默认)
BASE64Encoder encoder = new BASE64Encoder();
//最终传递的参数
String username = encoder.encode(bytes);
System.out.println(username);
}
}
POM引用
<!-- https://mvnrepository.com/artifact/org.bitbucket.b_c/jose4j -->
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.6</version>
</dependency>
AES方式:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class xxxx {
public static void main(String[] args) throws Exception {
String username = System.currentTimeMillis() + "_zhangsan";
String key = "XrYOSEYikzf1aww9Szp3QnZ4dB3LkkGq";
byte[] raw = key.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(username.getBytes("UTF-8"));
//转为base64(jdk默认)
BASE64Encoder encoder = new BASE64Encoder();
System.out.println(encoder.encode(encrypted));
}
}
5.3. 调用IDaaS获取用户授权的应用接口¶
门户中往往需要展示一个用户所拥有的应用的列表, 这可以通过调用一个接口来获取。
请求地址: /api/bff/v1.2/enduser/portal/sso/app_list
请求方法: GET
接口描述: 通过用户的访问令牌access_token获取用户所有授权可单点登录的应用。
响应主要字段格式示例如下
{
"success":true,
"code":"200",
"message":null,
"requestId":"1620373926480$c1112d2b-114d-237b-59ad-fe2f5d18eee8",
"data":{
"authorizationApplications":[
{
"name":"OAuth2",
"applicationId":"goverplugin_oauth2",
"applicationUuid":"2761c1936dbddbef56f3cec7bc885da6grO48gmRExY",
"idpApplicationId":"plugin_oauth2",
"logoUuid":"a4d0e4f81f4ad3f882a0128fc0d62a19mQzh887ke2p",
"startUrl":"http://127.0.0.1:81/api/bff/v1.2/enduser/portal/sso/go_2761c1936dbddbef56f3cec7bc885da6grO48gmRExY",
"createTime":"2021-04-20 14:09",
"description":"OAuth 是一个开放的资源授权协议,应用可以通过 OAuth 获取到令牌 access_token,并携带令牌来服务端请求用户资源。应用可以使用 OAuth 应用模板来实现统一身份管理。",
"enabled":true,
"supportDeviceTypes":["WEB"],
"existAccountLinking":false,
"enableTwoFactor":false,
"display":true,
"defaultLinking":true,
"autoLogin":false,
"classifyUuid":null,
"orderId":0
}
]
}
}
请求参数
Headers
参数名称 |
参数值 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
Authorization |
Bearer {access_token} |
是 |
eyJhbGciOiJI… |
用户令牌access_token,请求头中格式为 |
返回数据
名称 |
类型 |
是否必须 |
默认值 |
备注 |
其他信息 |
---|---|---|---|---|---|
success |
boolean |
必须 |
是否成功 |
||
code |
string |
必须 |
错误码,200则视为成功 |
||
message |
null |
必须 |
错误信息,code为非200时,可参考此值看错误信息 |
||
data |
object |
必须 |
|||
├─ |
object [] |
必须 |
授权可单点登录的应用列表 |
item 类型:object |
|
├─ |
string |
必须 |
应用名 |
||
├─ |
string |
必须 |
应用ID |
||
├─ |
string |
必须 |
应用uuid |
||
├─ |
string |
必须 |
应用模板ID |
||
├─ |
string |
必须 |
应用图标uuid |
||
├─ |
string |
必须 |
应用单点登录地址 |
||
├─ |
string |
必须 |
创建时间 |
||
├─ |
string |
必须 |
应用模板描述 |
||
├─ |
boolean |
必须 |
是否启用 |
||
├─ |
string [] |
必须 |
支持的设备类型 |
item 类型:string |
|
├─ |
非必须 |
||||
├─ |
boolean |
必须 |
是否存在子账户 |
||
├─ |
boolean |
必须 |
应用是否开启二次认证 |
||
├─ |
boolean |
必须 |
是否显示 |
||
├─ |
number |
必须 |
应用排序号 |
其中startUrl为单点登录应用系统的URL。
支持可选参数如下:
参数名称 |
是否必须 |
示例 |
备注 |
---|---|---|---|
access_token |
否 |
eyJhbGciOiJI… |
用户令牌access_token |
redirect_url |
否 |
http://www.xxx.com |
应用系统支持的回调地址,需要urlencode |
timestamp |
否 |
unix时间戳13位 |
当前时间,注意有效期默认5分钟 |
sign |
否 |
7148ce32446a6ad6141c2d7b911b81a441765639c125f736a6ffa9abf2c6063a |
签名值 |
注意:需要单点登录到应用系统,针对不同的登录方式,实现方式有所不一样:
从4.18.2开始,所有重新创建的应用,需要对接自定义门户的都需要加密跳转
已经对接的应用,无需修改,兼容老版本方式,请尽快升级成加密方式跳转
登录方式 |
是否有IDaaS主会话 |
跳转方式 |
---|---|---|
方式一:使用IDaaS统一登录页登录 |
是 |
|
方式二:调用IDaaS的接口进行登录 |
否 |
|
方式三:使用IDaaS无密码登录 |
否 |
|
5.3.1. 加密算法¶
保证token传输安全性,防止重放攻击,url传递token需要生成签名,进行加密跳转,加密算法如下:
第一步:将所有URL参数通过key升序排序,并去掉空值,拼接成url参数形式(key+value&key=value)
如参数列表: access_token=123×tamp=1667465505282&redirectUrl= 调换后为:access_token=123×tamp=1667465505282
第二步:将拼接后的字符串进行sha256加密,apiSecret当盐值
accessToken=123×tamp=1667465505282 签名后值为 : 7148ce32446a6ad6141c2d7b911b81a441765639c125f736a6ffa9abf2c6063a
第三步:将签名参数放入参数中,并进行跳转
最终跳转路径如下: https://{IDaaS_server}/api/bff/v1.2/enduser/portal/sso/go_xxxx?access_token=123×tamp=1667465505282&sign=7148ce32446a6ad6141c2d7b911b81a441765639c125f736a6ffa9abf2c6063a
示例代码(JAVA):
/*
* Copyright (c) 2016 BeiJing JZYT Technology Co. Ltd
* www.idsmanager.com
* All rights reserved.
*
* This software is the confidential and proprietary information of
* BeiJing JZYT Technology Co. Ltd ("Confidential Information").
* You shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement you
* entered into with BeiJing JZYT Technology Co. Ltd.
*/
package com.perry.demo;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 2022/11/4
*
* @author zouzheng
*/
public class OAuthPortalDemo {
public static void main(String[] args) {
System.out.println(getSsoUrl());
}
/**
* SSO发起地址签名:
* 步骤:
* 1. 将所有URL参数通过key升序排序,并去掉空值,拼接成url参数形式(key+value&key=value)
* 2. 将拼接后的字符串进行sha256加密,apiSecret当盐值
* 3. 将签名参数放入参数中,并进行跳转
*/
private static String getSsoUrl() {
Map<String, String> params = new HashMap<>(3);
//前提: 准备参数
//回调地址,非必传
params.put("redirectUrl", "");
//oauth token
params.put("access_token", "123");
//当前时间戳,注意SSO会校验,5分钟内有效
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
//step one : 将所有URL参数通过key升序排序,并去掉空值,拼接成url参数形式(key+value&key=value)
final String paramsString = params.entrySet().stream()
.filter(e -> StringUtils.isNotBlank(e.getValue()))
.sorted(Map.Entry.comparingByKey(String::compareTo))
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
//step two : 将拼接后的字符串进行sha256加密,clientSecret当盐值
//access_token=123×tamp=1667465505282 签名后值为 d827c016f336676e23affa583e985bcbcc4772ec4d370afd7b2dc98acaa85c9d
String apiSecret = "123456";
//示例2 ,这里用commons-codec开源库
final String sign = DigestUtils.sha256Hex(paramsString + apiSecret);
System.out.println(sign);
//示例2,用java原生方法
final String sign2 = sha256Hex(paramsString, apiSecret);
//step three : 将签名参数放入参数中,进行跳转
//门户SSO发起地址,同原有发起地址,注意后面的UUID需要替换
String ssoUrl = "https://{IDP_HOST}/api/bff/v1.2/enduser/portal/sso/go_{uuid}?";
return ssoUrl + paramsString + "&sign=" + sign;
}
public static String sha256Hex(String password, String salt) {
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes());
md.update(salt.getBytes());
byte[] bytes = md.digest();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return generatedPassword;
}
}
5.4. 门户衔接应用代办¶
门户中通常会挂应用内代办或者消息内容,门户开发者希望可以通过IDaaS将此类流程串联起来,应用将应用代办消息推送给门户,这条消息除了内容之外一般还包含一个回跳地址,门户上文接口获取的 startUrl加上回跳地址打通门户到应用消息的认证路径,进入应用后应用自主处理跳转地址,接口信息如下:
请求地址: {startUrl}?redirect_url={应用回跳地址}
请求方法: 浏览器跳转
注意,不同登录方式,访问方式有所不同,具体见上文
5.5. 对接IDaaS退出¶
当门户退出时,可调用IDaaS提供的全局统一退出地址,并传递一个门户portal的登录地址redirect_url,待IDaaS注销会话和注销用户的访问令牌后,则会重定向至门户的登录地址,接口地址信息如下:
请求地址: /public/sp/slo/{appId}
请求方法: GET,POST
请求参数:
参数名称 |
参数类型 |
是否必须 |
示例 |
备注 |
---|---|---|---|---|
appId |
text |
是 |
idaasoauth2 |
应用ID,此处为门户应用 |
redirect_url |
text |
否 |
https%3A%2F%2Fwww.idsmanager.com%2F |
退出成功后跳转的URL地址,可选(若不传或为空则跳转回IDP登录页),此处传递门户登录地址,注意使用urlencode |
access_token |
text |
否 |
xxxxxxx |
IDP签发的用户令牌access_token,可选,若有值则将access_token置为无效状态 |
请求示例:http://{IDaaS_server}/public/sp/slo/idaasoauth2?access_token={access_token}&redirect_url={portal_login_url}
注意事项:推荐使用POST请求方式,将参数redirect_url与access_token放在form表单中提交。
更多信息参考文档【单点退出(SLO)】-【SP SLO】-【3.1 全局退出】
6. 常见问题¶
6.1. 如何通过用户access_token获取用户更多信息¶
在获取到AT(Access Token)后,门户可以接着向IDaaS发送进一步的请求, 以获取到用户信息
Request URI: https://{IDaaS_server}/api/bff/v1.2/oauth2/userinfo
调用接口时传入access_token有两种方式:
URL值后:URL?access_token={access_token}
Header里面:Authorization Bearer {access_token}(注意 bearer与access_token之间的空格)
推荐使用header方式
接口说明: 获取用户详细信息
请求方式: GET
返回参数响应示例
{
"success": true,
"code": "200",
"message": null,
"requestId": "149DA248-8F49-4820-B87A-5EA36D932354",
"data": {
"sub": "823071756087671783",
"ou_id": "2079225187122667069",
"nickname": "test",
"phone_number": 11136618971,
"ou_name": "研发部",
"email": "test@test.com",
"username": "test"
}
}
参数说明
参数 |
类型 |
示例值 |
描述 |
---|---|---|---|
success |
boolean |
true |
是否成功 |
code |
String |
200 |
状态码 |
message |
String |
null |
返回消息 |
requestId |
String |
B3776BB1-930F-4581-B4C3-18F2D7D136CA |
请求ID |
data |
Object |
响应数据 |
|
└sub |
String |
823071756087671783 |
外部ID,唯一,可作主键 |
└ouid |
String |
2079225187122667069 |
父组织外部ID,唯一,可作主键 |
└nickname |
String |
test |
昵称 |
└phone_number |
String |
11136618971 |
手机号 |
└ou_name |
String |
研发部 |
父组织名称 |
String |
test@test.com |
邮箱 |
|
└username |
String |
test |
用户名 |
错误码说明
HttpCode |
错误码 |
错误信息 |
描述 |
---|---|---|---|
401 |
Unauthorized |
Unauthorized |
未授权的访问 |
403 |
Forbidden |
Forbidden |
无权限访问 |
404 |
ResourceNotFound |
ResourceNotFound |
访问的资源不存在 |
415 |
UnsupportedMediaType |
UnsupportedMediaType |
不支持的媒体类型 |
500 |
InternalError |
The request processing has failed due to some unknown error, exception or failure. |
发生未知错误 |
7. 附录¶
7.1. IDaaS Portal接入Demo¶¶
链接地址:https://github.com/aliyun-idaas/idp4-developer-portal-demo
克隆后请先查看README.md文档参考步骤修改对应参数进行测试。