单点登录(SSO)¶
最后更新:2021-12-31
概述¶
本文档主要覆盖一个WEB应用, 如何通过单点登录协议JWT, SAML等, 从IDP到这个应用的SSO操作。
如果是希望提供一个IDP的应用模版JAR, 不在本文档讨论范围内,请参考【 应用模版 】插件开发。
单点登录(SSO),英文全称为 Single Sign On。 SSO
是指在多个应用系统中,用户只需要登录一次,就可以访问所有相互信任的应用系统。IDaaS SSO 服务用于解决同一公司不同业务应用之间的身份认证问题,只需要登录一次,即可访问所有添加的应用。此服务可以涵盖用户在公有云和私有云中的双重需求。
单点登录场景¶
在单点登录实现过程中,现已满足以下场景,包括:
IDP 发起
SP 发起
接口后置
IDP 发起¶
通常政企有自己的门户, 希望能实现登录到门户后, 跳转到一个想去的SP应用,上述的流程即 IDaaS
发起。用户登录 IDaaS
平台,从 IDaaS
登录到 SP 应用场景,下面以 JWT
和 SAML
的实现为例来阐述 IDP
发起的单点登录流程:
以 JWT
协议 IDP
发起的单点登录为例 (SAML
的流程类似):
用户访问 IDaaS 登录页面输入用户名和密码进行登录
浏览器携带用户名密码向 IDaaS 请求登录
IDaaS 认证通过后,创建主 session ,并返回应用列表给浏览器
IDaaS 登录成功,用户可以看到 IDaaS 展示的应用列表
用户点击应用列表中的 SP1 应用图标
浏览器携带 SP1 应用的应用 id,向 IDaaS 请求生成 SP1 应用子 token
IDaaS 根据信息生成子 token 并返回给浏览器
浏览器携带子 token ,向 SP1 请求登录,如:http://idp/{applicationId}/public/sso/xxx?id_token=xxx&target_url=yyy
如果是SAML,浏览器携带
SAMLResponse
跳转到 SP1 的 URL {locationURL}?SAMLResponse=xxx(如果应用配置了IDP发起登录地址则会向配置的地址 [idpSSOLocationURL
] 发送SAMLResponse
)
SP1 应用系统解析获取的子 token ,验证通过后,创建子 session 并返回登录后页面
SP1 系统单点登录成功,浏览器显示 SP1 系统登录后页面
(16)表示 SP2 系统的单点登录流程,与 SP1 系统步骤一致,即在主 session 创建后,任何一个可单点登录应用进行单点登录的流程只需重复(5) 即可
SP 发起¶
通常政企有自己的应用, 希望能实现跳转到这个指定的应用的某个页面后,能再跳转回IDP,登录后再回到这个指定的SP页面, 这个过程就是SP发起的SSO。 SP发起主要是跳转到一个想去的SP应用SP 发起主要应用于 SSO 后可以跳回发起 SSO 的应用页面,下面以 JWT 和 SAML 的实现为例来阐述 SP 发起的单点登录流程。
用户访问 SP 资源, SP 会重定向到 IDaaS 的登录地址,在 IDaaS 认证通过后,会回到 SP 并携带 id_Token 和 target_url 参数,SP 校验后会向浏览器返回并显示用户访问的资源页面,如下图所示:
以 JWT
协议 SP 发起的单点登录为例 (SAML
的流程类似):
用户访问 SP 资源页面,如: http://sp/mail?id=1 ( JWT 中的 target_url,SAML 中的
RelayState
)浏览器向 SP 请求资源数据
SP 检测登录状态,若该用户已登录直接跳至步骤(10),未登录继续到(4)
携带 target_url 重定向到 IDaaS 登录页面,如:http://idp/enduser/sp/sso/xxx?enterpriseId=xxx&target_url=yyy
如果是SAML,则 AuthnRequest 请求发送至 IDP 的 URL 是 http://idp/enduser/api/application/{applicationId}/xxx/sp_sso?SAMLRequest=xxx&RelayState=yyy 注:安全起见,不应该直接传递 URL,RelayState 应该是一个随机数,即使是一个 URL (http%3A%2F%2Fsp%2Fmail%3Fid%3D1), 则SP应该先保存该 URL,生成一个随机数yyy,再传递到IDP。
用户输入 IDaaS 的账户和密码进行登录
浏览器携带账号密码请求登录 IDaaS
IDaaS 对账户进行认证并通过
认证通过后,会生成 id_token,并 302 跳转到 SP 到 SSO URL 地址 redirect_uri
如果是SAML,认证通过后,生成id_token,并 302 跳转到 SP 的 SSO 的地址 ACS URL。 注:ACS 是能校验解析 SAMLResponse 的一个 SP 提供的 SSO URL
浏览器携带 id_token 和 target_url 重定向到 SP,如:http://sp/public/sso/xxx?id_token=xxx&target_url=yyy
如果是SAML,浏览器携带 SAMLResponse 和 RelayState 重定向到 SP,如: http://sp/public/sso/xxx?SAMLResponse=xxx&RelayState=yy
校验成功后,创建 session 会话,向浏览器返回用户访问的 target_url 地址并进行跳转
如果是SAML,校验成功后,创建 session 会话,向浏览器返回用户访问的 RelayState 地址,并取出 URL,并进行 302 跳转
最终,用户查看到之前访问的 SP 资源页面,如:http://sp/mail?id=1
接口后置¶
通常政企有自己的应用, 希望能实现在这个应用登录后, 能把账户密码传递给IDP做比对, 成功后再打开这个应用的页面, 这个过程严格上讲不是一个SSO单点登录的过程,更多的是一个统一密码的过程。SP 登录时使用 IDaaS 进行接口后置认证登录场景,如下图所示:
用户访问 SP1 登录页面
浏览器请求 SP1 登录页面
SP 1 返回其登录页面
用户看到 SP1 登录页面
用户输入用户名密码进行登录
浏览器携带用户名、密码向 SP1 发起登录请求
SP 1 使用 IDaaS 接口进行认证,将用户名、密码传递给 IDaaS 进行认证登录
IDaaS 进行认证后,生成主 token ,并返回主 token 和应用列表以及用户信息给 SP1
登录 SP1 成功,浏览器获取到 SP1 登录后页面
用户可看到 SP1 登录后页面,可看到显示的应用列表
用户在 SP1 显示的应用列表中点击 SP2 应用图标进行单点登录
浏览器携带主 token 和应用id,向 IDaaS 请求生成子 token
IDaaS 返回 SP2 的子 token 以及 SP2 重定向地址
浏览器携带子 token 访问 SP2 重定向地址
SP2 解析子 token ,验证成功,并返回 SP2 登录后页面
用户看到 SP2 登录后页面, SP2 应用系统登录成功
开发者须知¶
文档中 IDaaS-Base-URL 需要替换为当前访问地址的主域,文中接口地址前也都需要替换主域地址;接口地址中的版本号以当前使用系统版本为准,也可以查看开发者文档中右侧菜单顶部的接口版本。
用户通过登录 IDaaS 系统,能够在用户的主界面应用列表中看到自身有权限访问的应用列表。用户可以选择一个应用进行单点登录。
单点登录到第三方应用的过程,对于用户来说是透明过程。此过程无需用户填写第三方应用系统的账号信息,而是通过安全的协议交换令牌,直接验证身份进入应用系统。
最佳实践-快速对接单点登录(JWT实现)¶
简介¶
整个JWT SSO 的流程,是接收 IDaaS 平台向 callback url 发出的 id_token 参数(即 JWT 令牌),并使用我们提供的(或第三方提供的) JWT 解密库/方法对 JWT 进行解析,并验证身份。 你可以自己按照这个逻辑完成代码,也可以在我们提供的 代码/demo 的基础上进行修改。
实现原理¶
通过浏览器登录集成的 IDaaS 系统后,确认要单点登录的应用,发起 SSO 请求到 IDaaS 系统。
IDaaS 生成
token
令牌发送到业务应用。你的应用获取到
token
令牌,用我们提供的插件或方法解析token
令牌,解析成功获取到用户信息并验证后,重定向进行登录;如果解析失败则拒绝登录。
申请 JWT 应用¶
本章节默认你已经创建了应用。如果还未创建一个应用并审核的话,请前往 【准备开发 - 创建应用】。
本章节讲解开发者身份创建应用,如果使用IT管理员身份,请切换到开发者身份,或者查看 jwt-标准应用
第 1 步¶
应用创建成功后,会弹出提示弹窗,此时点击“新建 SSO 应用”,则可打开创建 JWT 服务弹窗,如下图:
参数说明:
图标: 业务应用的 logo 图片。
应用服务 ID: 自动生成服务 ID,且唯一。
应用名称: 代表该业务应用的名称
所属领域: 代表区分该应用服务类型是哪一种服务类型。
设备类型: 代表该服务支持的设备类型,标记使用。
JWT SSO 地址: 业务应用中的 JWT SSO 地址,在单点登录时 IDaaS 将向该地址用 GET 方式发送 id_token 信息,参数名为 id_token ,业务应用通过 id_token 与 PublicKey 可获取业务应用中的用户信息,应用可配置多个地址,如果在业务系统(SP)发起登录, 请求 SP 登录地址时如果携带 service 参数 IDaaS 会检验合法性,成功后会将浏览器会重定向到该地址,并携带 id_token 。
Redirect URL: 业务应用中在 JWT SSO 成功后重定向的 URL,一般用于跳转到二级菜单或者自定义的动态地址等。 (若在添加应用时设置了该URL,在 JWT SSO 时会以参数 redirect_url 优先传递该值。 若添加应用的时候未设置该值,发起 SSO 也可以自定义参数 redirect_url 的值)
token 包含应用列表: 配合 接口登录(RL)使用,是否在 JWT 的 token 中包含当前用户的授权应用列表。
账号关联方式:JWT 协议可以使用 账户关联 或 账户映射 方式进行单点登录。如果应用系统接收到的用户名希望和 IDaaS 系统中一致,可以使用账户映射;反之,若希望手动建立关联关系,则选择账户关联。
第 2 步¶
新建 JWT 服务成功后,弹出弹窗,此时可发布应用进行测试。
第 3 步¶
应用信息完成后,可以进行相对应的操作,发布按钮,确定操作,应用状态为已发布后即可开始进行集成开发。如下图:
第 4 步¶
导出公钥。点击查看应用->详细,在 PublicKey 字段下方有导出 PKCS8 公钥按钮。
PHP
或Python
:请点击导出该应用的专属公钥,导出后为一个 .pem 文件,将其放置在你的应用中安全并能够度读取到的位置,该公钥在集成中会被用来解密 JWT 信息;
Java
或.NET
: ,请直接复制 PublicKey 框内的内容(即 JWK, Json Web Key)并当做公钥进行解密,将其存储在安全的位置,调试时可以暂时直接存为字符串变量
至此准备工作完成,我们开始进行服务器端的快速开发。
IDP发起¶
IDP SSO URL¶
复制应用详情中的IDaaS SSO URL即可,格式如下
http://idp/enduser/bff/sso/go_xxx?_enterprise_id=xx&purchaseId=xxx
集成方式¶
用户登录 IDaaS
系统,点击应用列表中的 SP1 应用图标即可:
以 JWT
协议 IDP 发起的单点登录为例 :
用户访问 IDaaS 登录页面输入用户名和密码进行登录
浏览器携带用户名密码向 IDaaS 请求登录
IDaaS 认证通过后,创建主 session ,并返回应用列表给浏览器
IDaaS 登录成功,用户可以看到 IDaaS 展示的应用列表
用户点击应用列表中的 SP1 应用图标
浏览器携带 SP1 应用的应用 id,向 IDaaS 请求生成 SP1 应用子 token
IDaaS 根据信息生成子 token 并返回给浏览器
浏览器携带子 token ,向 SP1 请求登录,如:http://idp/{applicationId}/public/sso/xxx?id_token=xxx&target_url=yyy
SP1 应用系统解析获取的子 token ,验证通过后,创建子 session 并返回登录后页面
SP1 系统单点登录成功,浏览器显示 SP1 系统登录后页面
(16)表示 SP2 系统的单点登录流程,与 SP1 系统步骤一致,即在主 session 创建后,任何一个可单点登录应用进行单点登录的流程只需重复(5)即可
SP发起¶
SP SSO URL¶
复制应用详情中的SP SSO URL即可,格式如下
http://idp/enduser/sp/sso/xxx?enterpriseId=xxx&target_url=yyy
集成方式¶
用户访问SP系统资源,重定向到sp sso url即可:
以 JWT
协议 SP 发起的单点登录为例 :
用户访问 SP 资源页面,如: http://sp/mail?id=1 ( JWT 中的 target_url)
浏览器向 SP 请求资源数据
SP 检测登录状态,若该用户已登录直接跳至步骤(10),未登录继续到(4)
携带 target_url 重定向到 IDaaS 登录页面,如:http://idp/enduser/sp/sso/xxx?enterpriseId=xxx&target_url=http%3A%2F%2Fsp%2Fmail%3Fid%3D1
用户输入 IDaaS 的账户和密码进行登录
浏览器携带账号密码请求登录 IDaaS
IDaaS 对账户进行认证并通过
认证通过后,会生成 id_token,并 302 跳转到 SP 到 SSO URL 地址 redirect_uri
浏览器携带 id_token 和 target_url 重定向到 SP,如:http://sp/public/sso/xxx?id_token=xxx&target_url=yyy
校验成功后,创建 session 会话,向浏览器返回用户访问的 target_url 地址并进行跳转
最终,用户查看到之前访问的 SP 资源页面,如:http://sp/mail?id=1
JAVA 集成¶
配置环境¶
JDK 1.6 以上
SDK 和对接示例可以通过 【单点登录 - 相关下载】 下载参考。
接收令牌¶
/**
* id_token是IDaaS请求时带来的,参数名为 "id_token" , 支持使用GET/POST两种方式放入;
* PublicKey是在IDaaS里注册应用时生成的,可在应用详情页中JWT PublicKey查看;
* target_url是IDaaS里注册应用时设置的target_url,此示例代码是通过id_token和PublicKey解析用户信息并完成单点登录。
*/
@RequestMapping(value = "/public/sso/{id}", method = {RequestMethod.GET, RequestMethod.POST})
public String ssoUrl(@RequestParam String id_token, @PathVariable("id") String id, String target_url, Model model, HttpServletRequest request) throws UnsupportedEncodingException {
//1.接收方法,GET和POST均支持
//2.<解析令牌>为解析id_token并验证用户信息
SSOConfigDto config = ssoConfigService.findSSOConfigById(id);
if (null == config) {
model.addAttribute("error", "system config publicKey do not EXIST");
return "error";
}
return checkUsername(id_token, target_url, model, request,config.getPublicKey());
}
解析令牌¶
PublicKey: 解析令牌的过程中,我们会使用到应用的 PublicKey。请在 JWT 应用 -> 详细 中将PublicKey字段对应的内容拷贝并存储起来。
private String checkUsername(String id_token, String target_url, Model model, HttpServletRequest request, String publickey) throws UnsupportedEncodingException {
//1.使用公钥解析id_token,使用publicKey解密上一步获取的id_token令牌
DingdangUserRetriever retriever = new DingdangUserRetriever(id_token, publickey);
DingdangUserRetriever.User user = null;
try {
//2.获取用户信息
user = retriever.retrieve();
} catch (Exception e) {
LOG.warn("Retrieve Username error", e);
model.addAttribute("error", "Retrieve Username error: " + e.getMessage());
return "error";
}
//3.如果publicKey不正确或者id_token过期,获取到的用户信息可能为null
if (null == user) {
model.addAttribute("error", "wrong request,not found Username from id_token or id_token has expired");
return "error";
}
//4.获取到用户信息,检测用户名是否存在自己的业务系统中,isExistedUsername方法为示例实现
if (userService.isExistedUsername(user.getUsername())) {
//5.如果存在,登录成功,返回登录成功后的界面
User sysUser = userService.updateLoginTimes(user.getUsername());
HttpSession session = request.getSession();
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, saveSecurity(sysUser));
//6.如果注册应用时添加了target_url,那么返回此自定义url页面
if (StringUtils.isNotEmpty(target_url)) {
return "redirect:" + target_url;
}
//7.否则返回系统默认操作页面
return "redirect:../../index";
} else {
//8.如果不存在,返回登录失败页面,提示用户不存在
model.addAttribute("error", "username { " + user.getUsername() + " } not exist");
return "error";
}
}
PHP 集成¶
配置环境¶
在本例中,我们使用 composer
管理第三方库(可选)
公钥:开发前需要在IT管理员权限下前往应用->详细->导出PKCS8公钥来获取解密 JWT
用的公钥,并安全地放置在能访问到的目录内。
接收令牌¶
JWT
的 id_token 将会以url参数的方式传进callback
页面,我们直接将其读取出来:
/* 使用composer 载入 php-JWT第三方库
* 命令行 composer require firebase/php-JWT
* 库链接:https://github.com/firebase/php-JWT
* 我们使用Firebase的这个第三方库来实现对JWT的解密,如果不用composer的话,请自行添加源文件
* 你也可以使用其他能对 JWT token 进行RS256解码的工具或库
*/
// 在这里将 JWT 库引入,在这里为了便捷demo直接使用
// 推荐使用
require 'vendor/autoload.php';
use \Firebase\ JWT \ JWT ;
// 本地存储public key公钥的位置
$public_key_location = "LOCATION/TO/YOUR/PUBLIC-KEY/XXX.pem";
// 读取公钥信息,公钥在这里存储在一个.pem文件内
$public_key = file_get_contents($public_key_location);
// 从url的参数中读取 id_token ,即令牌
if (!empty($_GET[" id_token "])) {
$JWT = $_GET[" id_token "];
// 这里继续第二步:解析令牌
}
解析令牌¶
通过第三方库 php-JWT,使用公钥对收到的 JWT token (即 id_token )进行解密,获取到用户信息并错误码为0的话,验证通过跳转到登录页面,失败则拒绝:
try {
/**
* You can add a leeway to account for when there is a clock skew times between
* the signing and verifying servers. It is recommended that this leeway should
* not be bigger than a few minutes.
*
* Source: http://self-issued.info/docs/draft-ietf-oauth-json-web- token .html#nbfDef
*/
// Firebase的 JWT 库的一个参数,不出问题的话可以忽略
// (可选)当服务器时间与本地时间不符时,可以通过这个leeway参数来调整容错
JWT::$leeway = 60; // $leeway in seconds
// 使用公钥、使用RS256算法对 JWT (即第一步传进来的 id_token )进行解密进行解密
$decoded = JWT::decode($JWT, $public_key, array('RS256'));
// 将解密的结果从class转化成PHP array
$decoded_array = (array) $decoded;
// 打印出解密的结果,成功!
print("解密结果:<br>");
foreach ($decoded_array as $key => $value) {
print $key . ": " . $value . "<br>";
}
// 获取到用户信息后,判断该用户是否存在于你的系统内
if (userExistsInSystem()) {
// 如果存在,那么登录成功,跳转到登录后页面
} else {
// 如果不存在,那么登录失败,跳转到显示错误页面
}
} catch(Exception $e) {
print "错误:" . $e->getMessage();
}
.NET 集成¶
配置环境¶
.NET Framework 4及以上
SDK 和对接示例可以通过 【单点登录 - 相关下载】 下载参考。
接收令牌¶
// id_token 是 IDaaS 请求时带来的,在body里获取,PublicKey是在 IDaaS 里注册应用时生成的,注册完可见,此示例代码是获取用户信息。
// JWT SSO
[Route("jwt/sso/login")]
public ActionResult ssoUrl(String id_token )
{
//1.接收方法为GET方式,参数名为 id_token
//2.<解析令牌>为解析 id_token 并验证代码
}
解析令牌¶
PublicKey
: 解析令牌的过程中,我们会使用到应用的PublicKey。请在 JWT 应用->详细 中将PublicKey字段对应的内容拷贝并存储起来。
//1. 使用公钥,解析 id_token
string username;
DingdangSDK.DingdangUserRetriever retriever = new DingdangSDK.DingdangUserRetriever( id_token, PublicKey);
DingdangSDK.User user = null;
//2. 获取用户信息
user = retriever.retrieve();
username = user.sub;
//3. 判断用户名是否在自己系统存在
//4. 如果存在,登录成功,返回登录成功后的页面
//5. 如果注册时添加target_url,那么返回此自定义url页面
//6. 否则返回系统默认操作页面
//7. 如果不存在,返回登录失败页面,提示用户不存在
Python 集成¶
下载资源库¶
本Python JWT 示例使用Py JWT 库来进行 JWT 的解密
公钥:开发前需要在IT管理员权限下前往应用->详细->导出 PKCS8 公钥来获取解密 JWT
用的公钥,并安全地放置在能访问到的目录内。
JWT
的 token
将会以url参数的方式传进来,解密后进行认证判断
// 库的 github 链接 https://github.com/jpadilla/pyJWT
pip install Py JWT
// 注:CentOS系统如果使用时无法导入算法 RSAAlgorthm时需要下载pyJWT的2个依赖包
yum install ibffi-devel
pip install cryptography
接收令牌¶
def get_id token ( token ):
if not token .strip():
print(' token 信息不能为空')
else:
//这里继续第二步:解析令牌
get_id token (my_id token ); // 运行程序
解析令牌¶
# 2.解析令牌
通过JWT解密库,使用公钥对传入的 id_token 进行解密。将公钥以字符串的形式从文件中读取出来,并作为key进行解密:
// 引入用到的包文件
import JWT
import json
from JWT.algorithms import RSAAlgorithm
from JWT.utils import force_bytes
from utils import key_path // 本例中key_path辅助方法是写在utils工具类中的
def get_user_ifon( id_token ):
try:
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
pem_key = open(key_path('D:\pythonDemo\key\public_key_pkc8.pem'), 'r')
public_key = algo.prepare_key(pem_key.read())
token _info = JWT.decode(force_bytes( id_token ), key=public_key,verify=True)
user_info = json.loads(json.dumps( token _info))
username = user_info['sub']
print(username)
# 3.判断用户名是否在自己系统存在
# 4.如果存在, 登录成功,返回登录成功后的页面
# 5.如果注册时添加target_url,那么返回此自定义url页面
# 6.否则返回系统默认操作页面
# 7. 如果不存在, 返回登录失败页面, 提示用户不存在
except Exception as e:
print(e)
上面用到的key_path方法是用来获取public key位置的辅助方法,具体如下:
def key_path(key_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'keys', key_name)
处理结果¶
解析后数据的数据为JSON,校验数据后进行登录处理
请求成功示例数据:
{
"username":"zhangsan",
"state","",
error:"0",
"errorMes":"operation success"
}
返回参数:
常量 |
说明 |
---|---|
username |
子账户(业务应用)用户名 |
state |
自定义参数,原值返回 |
error |
错误码,0 表示成功 |
errorMsg |
错误信息描述 |
相关下载¶
JAVA 相关下载¶
PHP 相关下载¶
.NET 相关下载¶
Python 相关下载¶
常见问题¶
Q:如何确定使用哪个协议模板对接,是否需要开发¶
A:如果是支持标准协议的,如jira,confluence,可以自行在管理员侧选择对应模板,如SAML模板进行配置; 如果是登录方式简单的,只有用户名和密码登录,并且不是前后端分离,可以尝试使用表单代填自己对接; 如果上面两种方式不行,那么应用需要改造以对接单点登录,IDaaS推荐使用JWT方式,集成简单,直接集成IDaaS提供的SDK实现。
Q:每个用户登录后,系统会返回一个 token
,这个 token
在我们服务器端是永远生效的还是我们要注销之后才会失效?业务系统怎么保持登录状态,是每次页面请求需要请求 token
吗?¶
A:token
是有一定的时效的,业务系统得到我们的 token
后可以建立自己系统的 session 机制。session 当然有自己的 timeout 设定, 有了 session 以后,每次业务应用的访问和我们是无关的。 每一次获取到 token 的请求 IDaaS 都会返回一个状态码,可以根据我们返回的状态码来判断 token
是否失效,如果失效就通过刷新 token
来重新获取 session 。
Q:JDK 版本问题,导致 jar 包无法使用?¶
A:根据 JDK 的版本提供了 3 个版本的 jar 包,1.8 版本的可以直接导入 jar ,并且添加依赖。JDK1.6 或者 JDK1.7 需要手动导入相关的依赖 jar 包。请根据自己的 jdk 版本选择相对应的 jar 包。
更多实践¶
接口后置¶
通常政企有自己的应用, 希望能实现在这个应用登录后, 能把账户密码传递给IDP做比对, 成功后再打开这个应用的页面, 这个过程严格上讲不是一个SSO单点登录的过程,更多的是一个统一密码的过程。SP 登录时使用 IDaaS 进行接口后置认证登录场景,如下图所示:
实现原理¶
获取 id_token¶
IPG的认证平台负责id_token的分发,需要进行认证的账号已经在IPG平台的情况下,也可以对用户的身份进行自己认证;如果IPG没有用户信息,不能自己进行认证,则需要传递到开发者的应用服务器,由应用服务器进行身份认证,并返回结果给IPG。具体流程如下图:
步骤分解:
应用携带账号认证信息向IPG发出id_token申请,认证信息可以使(账户名\手机号\邮箱)+密码,也可以是(账户名\手机号\邮箱)+APP动态令牌
IPG如果可以自行校验,直接根据情况返回校验结果以及id_token。如果不能自行校验,会将信息传递到应用服务器开发的接口,请求应用服务器对认证信息进行验证。
应用服务器验证完认证信息返回结果。
IPG从应用服务器接收到验证成功或失败的结果,并根据情况返回id_token给应用。
如果在创建应用的时候启用了Refresh Token,那么在最后一步从IPG认证服务器还会返回一个refresh_token给应用。应用可以将其存储起来,用来进行id_token过期并需要刷新的请求。
使用 id_token¶
开发者可以解析id_token获取用户信息,可以使用publickey对id_token进行校验:
获取信息:解析jwt格式的id_token,获取其中包含的用户信息
解密验证:使用公钥进行校验/解密,如果成功的话,那么认证即成功,请求可以通过。
刷新 id_token¶
如果添加的API令牌已经启用了Refresh Token功能,当id_token过期的时候,可以通过refresh_token去刷新换取一个新的id_token。
如果没有启用Refresh Token功能,那么过期后需要按照获取id_token的方式重新用身份认证信息去IPG获取。
步骤分解:
应用携带refresh_token向IPG进行刷新id_token请求
IPG返回新的id_token给应用
申请 STS 服务¶
本章节默认你已经创建了应用。如果还未创建一个应用并审核的话,请前往 【准备开发 - 创建应用】。
在开发之前,我们需要先申请一个STS服务,对应我们要开发的STS功能。
在开发者的后台管理系统中,选择左侧的列表中选择API令牌(STS),选择添加API令牌(STS)。如下图:
2. 填写STS应用信息。如下图(填写内容的解说在图片下方):
表单字段名称 |
说明 |
---|---|
应用ID |
默认生成的,应用的唯一标识 |
OIDC KeyId |
默认生成的唯一标识,在服务器端向IPG认证服务器请求获取公钥的时候会使用到 |
应用 |
选择要添加的应用 |
ID Token有效期 |
ID Token有效期,单位: 秒,默认7200(2小时) |
Refresh Token有效期 |
Refresh Token有效期, 单位: 天, 默认30 |
启用Refresh Token |
是否启用Refresh Token有效期 |
认证方式 |
使用哪种方式进行认证 |
可授权范围 |
认证成功后IDaaS返回的ID Token中是包含字段信息 |
备注信息 |
可选项,可以备注应用的相关信息 |
添加应用完成后,需要发布该应用才可进行开发调试。
4. 应用发布完成后,回到 API令牌(STS) 列表,在应用的操作中选择启用,确定后即可集成使用STS服务。如下图:
5. 点击详细查看添加的STS应用的详细信息。如下图:
6. 也可点击操作中的“文档”,进入开发者文档,点击左上角“应用详情”查看当前应用的详情。
API¶
文档中的“IDaaS_server”需要替换为当前访问地址的主域,文中接口地址前也都需要替换主域地址;接口地址中的版本号以当前使用系统版本为准,也可以查看开发者文档中右侧菜单顶部的接口版本。
我们为开发者提供了服务器端对id_token进行解码校验的演示代码,可以在底部的相关下载中下载。
获取id_token¶
应用端使用,用于从IPG认证服务器获取 OIDC 的 id_token。
Request URI:
POST
/api/public/bff/v1.2/sts/retrieve_id_token
Content-Type: application/json
请求参数:
参数名 |
参数值 |
备注 |
---|---|---|
appKey |
{appKey} |
STS应用中的 appKey |
appSecret |
{appSecret} |
STS应用中的 appKey |
username |
{username} |
IDP账户登录名称,可以为账户名、邮箱、手机号 |
password |
{password} |
认证密码,根据所选认证方式,可以为账户密码、APP动态口令 |
请求Body示例:
{
"appKey":"a26abc61154e62cbe800e497c7556021EddePLV2SkZ",
"appSecret":"6RFBJh7RWfQZBwnTCBYUNYB4j2csUGzft4go17DMz6",
"username":"zhangsan",
"password":"Jzyt@2016"
}
响应:
{
"success": true,
"code": "200",
"message": null,
"requestId": "xxxx",
"data": {
"statusCode": 0,
"errors": [],
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "Bx8oVyd5aBD...",
"successful": true
}
}
statusCode
为0表示成功,表明账号密码是正确的。
HTTP响应码为401(Unauthorized)则表示失败,具体为: 400表示请求参数错误,483表示系统未找到refresh_token数据,484表示refresh_token 已过期,501表示账户名错误,479表示应用不再支持 refresh_token 操作。
得到id_token的公钥¶
应用服务器端使用,用于从IPG通过keyId获取OIDC Public Key公钥。
请求URI:
GET
/api/public/bff/v1.2/sts/load/public_key?keyId={keyId}
请求参数:
参数名 |
参数值 |
备注 |
---|---|---|
keyId |
{keyId} |
API应用的 keyId |
keyId:应用详情中的 OIDC KeyId
请求示例:
https://{IDaaS_server}/api/public/bff/v1.2/sts/load/public_key?keyId=fff39a73e4ad8649be690b5b2df49855BBnjkb6ICqC
响应:
{
"success": true,
"code": "200",
"message": null,
"requestId": "xxxxxx",
"data": {
"statusCode": 0,
"errors": [],
"publicKey": "eyJrdHkiOiJSU0EiLCJr...",
"successful": true
}
}
提示: 返回的publicKey是Base64 Encoded,使用时需要先调用Base64 Decoded。statusCode
为0表示成功,HTTP响应码为401(Unauthorized)则表示失败,具体为: 233表示未找到对应的keyId,482表示应用尚未启用。
刷新id_token¶
应用端使用,用于从IDP通过refresh_token获取id_token
请求URI:
POST
/api/public/bff/v1.2/sts/refresh_id_token
Content-Type: application/json
请求参数:
参数名 |
参数值 |
备注 |
---|---|---|
username |
{username} |
IDP账户名 |
refresh_token |
{refresh_token} |
获取id_token时返回的refresh_token |
请求Body示例:
{
"username":"zhangsan",
"refresh_token":"dK4ljtYf9TWTQX7Mgwi39Ol9p5H4md88kcgnL5nl2cChNdAn14mGNelA8CRLgHJK"
}
响应:
{
"success": true,
"code": "200",
"message": null,
"requestId": "xxxxxx",
"data": {
"statusCode": 0,
"errors": [],
"id_token": "eyJhbGciOiJSUzI1N...",
"refresh_token": "Bx8oVyd5aBDZ0DZqYLNSALdF...",
"successful": true
}
}
statusCode
为0表示成功,HTTP响应码为401(Unauthorized)则表示失败,具体为: 400表示请求参数错误,483表示系统未找到refresh_token数据,484表示refresh_token 已过期,501表示账户名错误,479表示应用不再支持 refresh_token 操作。
校验id_token¶
使用应用详情内的公钥(Publickey)对id_token进行校验
具体代码示例可参考【插件式SSO(JWT)】中的解析令牌模块
移动端集成¶
Android集成指南¶
在项目libs文件夹中加stslib,并在Android studio的build.gradle添加:
compile 'com.squareup.okhttp3:okhttp:3.3.1' compile 'com.google.code.gson:gson:2.4' compile files('libs/stslib1.0.jar')
在您的应用的AndroidManifest.xml中,申请访问网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
在Application中,初始化SDK:
STS.init(getApplicationContext());
如何使用SDK:
调用jar中的方法请求idToken:
STS.getInstance().getIdToken(headUrl, appKey, appSecret, username, password, listener) * @param headUrl 请求地址头部 * @param listener 请求接口TokenCallBack回调,若有界面操作,需要在接口回调中开启子线程处理 * @param appKey STS应用的appkey * @param appSecret appSecret * @param username IDP账户登录名称,可以为账户名、邮箱、手机号 * @param password 认证密码,根据所选认证方式,可以为账户密码、APP动态口令 在接口回调返回中带有idToken,refreshToken和errorCode
idToken过期后,调用jar中的方法刷新idToken:
STS.getInstance().refreshIdToken(headUrl, refreshToken, username,listener) * @param headUrl 请求地址头部 * @param listener 请求接口TokenCallBack回调,若有界面操作,需要在接口回调中开启子线程处理 * @param refreshToken refreshToken,用来获取新的id_token * @param username 用户名 在接口回调返回中带有idToken,refreshToken和errorCode
代码混淆:
如果您启用了代码混淆,请在您的 proguard-rules.pro 中添加以下代码:
-keep class com.idsmanager.stslibrary.** {*;} -dontwarn com.idsmanager.stslibrary.**
iOS集成指南¶
导入STSFramework包
在Targets - General 中下方有一栏叫做Embedded Binaries,将我们的STSFramework添加进去:
点击下方的 + 号
在Frameworks中选取STSFramework
成功将STSFramework添加到Embedded Binaries中
注意:如何没有配置这步会导致程序运行失败
获取id_token:
传入用户名、密码、appKey、appSercert,获取id_token
/** * @brief 获取id_token. * @param config 配置参数 * requestURL:请求地址 * username:IDP账户登录名称,可以为账户名、邮箱、手机号 * password:认证密码,根据所选认证方式,可以为账户密码、APP动态口令 * appKey:web端申请应用成功后,可在详细页面查看 * appSercert:web端申请应用成功后,可在详细页面查看 * success 返回获取的结果,详情可以查看错误码介绍. * failure 返回失败的原因. */ 调用示例: STSSDK.requestId_token(requestURL: "\(self.serverIp.text!)/api/public/bff/v1.2/sts/retrieve_id_token", username: self.username.text!, password: self.password.text!, appKey: "4dbfb4bfe9bb43f386d473e789db6776eIE2sxJlE7b", appSercert: "sVWx3VGXD5qmHTBvM6lWcFvOJOUHh5UTllrc7Z8FPr", success: { (result) in DispatchQueue.main.async(execute: { //刷新UI回到主线程 print(result) }) }) { (error) in Global.hideProgressHUD() print(error) }
启用refresh_token选项成功响应响应:
{ "success": true, "code": "200", "message": null, "requestId": "xxxx", "data": { "statusCode": 0, "errors": [], "id_token": "eyJhbGciOiJSUzI1NiIs...", "refresh_token": "Bx8oVyd5aBD...", "successful": true } }
刷新id_token:
传入用户名、refresh_token,刷新id_token
/** * @brief 刷新id_token. * @param config 配置参数 * requestURL:请求地址 * username:用户名 * refresh_token:第一次获取id_token会返回,用于刷新id_token * success 返回获取的结果,详情可以查看错误码介绍. * failure 返回失败的原因. * @return 返回获取的结果,详情可以查看错误码介绍. */ 调用示例: STSSDK.refreshId_token(requestURL: "\(self.serverIp)/api/public/bff/v1.2/sts/refresh_id_token", username: "86627161@qq.com", refresh_token: self.refreshToken, success: { (result) in DispatchQueue.main.async(execute: { //刷新UI回到主线程 print(result) }) }) { (error) in print(error) }
响应:
{ "success": true, "code": "200", "message": null, "requestId": "xxxxxx", "data": { "statusCode": 0, "errors": [], "id_token": "eyJhbGciOiJSUzI1N...", "refresh_token": "Bx8oVyd5aBDZ0DZqYLNSALdF...", "successful": true } }
错误码¶
Android 错误码¶
errorCode |
备注 |
---|---|
1 |
网络出错 |
2 |
服务器出错 |
400 |
请求参数错误 |
479 |
应用不支持refresh_token |
480 |
appKey或者appSecret错误 |
481 |
生成idToken时出错 |
482 |
应用没有启用 |
483 |
refresh_token没有找到 |
484 |
refresh_token过期 |
501 |
用户名或密码错误(在刷新idToken时,表示用户名错误) |
iOS 错误码¶
errorCode |
备注 |
---|---|
1400 |
参数错误 |
1401 |
用户名密码错误 |
1402 |
appKey或appSecret错误 |
1403 |
应用没有启用 |
1404 |
应用不支持refresh_token |
1405 |
生成idToken时出错 |
1406 |
refresh_token错误或不存在 |
1407 |
refresh_token过期 |
1408 |
未知错误 |
相关下载¶
JAVA 相关下载¶
Android 相关下载¶
IOS 相关下载¶
更多协议¶
在IDP平台中,通过提供’应用’(Application)来实现单点登录功能。提供两种类型的应用实现:
标准协议 应用,基于行业规范沉淀的与IDP平台沉淀的各类单点登录协议实现,包括:
定制模板 应用,对于不支持标准协议的但有自己SSO实现逻辑的应用(如 阿里邮箱),IDP提供应用插件能力进行二次开发,扩展将各类应用的 SSO能力集成到IDP中来,统一属于定制模板。