OIDC(OpenID Connect)是建立在OAuth 2.0基础上的一个认证协议,本文为您介绍应用如何使用OIDC获取阿里云登录用户的信息。
前提条件
获取用户登录信息前,您需要创建应用,设置应用名称、OAuth范围和回调地址等关键信息,并为应用生成应用密钥。具体操作,请参见创建应用、添加应用范围和创建应用密钥。
基本概念
概念 | 说明 |
身份令牌 | OIDC可以给应用下发代表登录用户的身份令牌。身份令牌用于获取姓名、登录名等用户信息,不能用于访问阿里云服务。 |
OIDC Discovery Endpoint | OIDC协议包含了不同的Endpoint用于不同的目的,Discovery Endpoint中包含OIDC协议所需要的所有配置信息,方便开发者使用。 说明 Discovery Endpoint是通过JSON文档来提供一系列键值,其中包含主要的提供者信息,例如:协议支持的响应类型、令牌颁发者的取值、身份令牌签名密钥的地址和签名算法等。 阿里云作为OIDC服务提供者,提供了一个Discovery Endpoint: Discovery Endpoint包含的内容示例如下:
|
基本流程
用户通过浏览器登录应用。
应用重定向到阿里云OIDC服务并将URL返回给浏览器。
说明如果用户还未登录,则会进一步重定向到阿里云登录服务。
用户通过浏览器登录阿里云OIDC服务并申请授权码。
阿里云OIDC服务重定向到应用并返回授权码给浏览器。
浏览器通过应用使用授权码向阿里云OIDC服务申请身份令牌。
阿里云OIDC服务向应用返回身份令牌和访问令牌,应用通过身份令牌或访问令牌便可以获取用户信息。
具体的使用场景如下:
直接解析身份令牌获取用户信息。
该场景需要应用验证身份令牌的签名。应用首先获取身份令牌签名密钥,然后验证身份令牌的JWT签名,最后使用解析后的用户信息。更多信息,请参见示例一:应用获取身份令牌签名密钥、示例二:验证身份令牌的JWT签名和示例三:解析身份令牌获取用户信息。
使用身份令牌在各个不同模块之间通信。
该场景需要应用验证身份令牌的签名。应用首先获取身份令牌签名密钥,然后验证身份令牌的JWT签名。更多信息,请参见示例一:应用获取身份令牌签名密钥和示例二:验证身份令牌的JWT签名。
使用访问令牌多次查询用户信息。
应用在获取访问令牌后,通过调用UserInfo接口获取用户信息。更多信息,请参见示例四:通过访问令牌和UserInfo接口获取用户信息。
示例一:应用获取身份令牌签名密钥
请求示例如下:
private List getSignPublicKey() {
HttpResponse response = HttpClientUtils.doGet("https://oauth.aliyun.com/v1/keys");
List rsaKeyList = new ArrayList();
if (response.getCode() == 200 && response.isSuccess()) {
String keys = JSON.parseObject(response.getData()).getString("keys");
try {
JSONArray publicKeyList = JSON.parseArray(keys);
for (Object object : publicKeyList) {
RSAKey rsaKey = RSAKey.parse(JSONObject.toJSONString(object));
rsaKeyList.add(rsaKey);
}
return rsaKeyList;
} catch (Exception e) {
LOG.info(e.getMessage());
}
}
LOG.info("GetSignPublicKey failed:{}", response.getData());
throw new AuthenticationException(response.getData());
}
示例二:验证身份令牌的JWT签名
阿里云颁发的身份令牌是带有签名的JWT(JSON Web Token),签名算法为JWS标准RS256。当应用请求获取用户信息时,阿里云需要对身份令牌进行验证,包含以下几个方面:
签名验证:通过OAuth服务公布的签名公钥,验证身份令牌的真实性和完整性。
请求示例如下:
public boolean verifySign(SignedJWT signedJWT) { List publicKeyList = getSignPublicKey(); RSAKey rsaKey = null; for (RSAKey key : publicKeyList) { if (signedJWT.getHeader().getKeyID().equals(key.getKeyID())) { rsaKey = key; } } if (rsaKey != null) { try { RSASSAVerifier verifier = new RSASSAVerifier(rsaKey.toRSAPublicKey()); if (signedJWT.verify(verifier)) { return true; } } catch (Exception e) { LOG.info("Verify exception:{}", e.getMessage()); } } throw new AuthenticationException("Can't verify signature for id token"); }
有效期验证:检查令牌颁发时间和令牌过期时间的有效性。
检查令牌接收者:防止颁发给其他应用的身份令牌被传递给本应用。
示例三:解析身份令牌获取用户信息
返回参数
Header返回参数
参数名称
描述
需要的OAuth范围
alg
签名算法。
openid
kid
验证身份令牌签名使用的公钥,用户需要使用此公钥验证签名,防止身份令牌被篡改。
openid
Body返回参数
参数名称
描述
需要的OAuth范围
exp
令牌过期时间戳。
openid
sub
唯一代表登录用户的字符串,但并不包含阿里云UID、用户名等信息。
说明当登录用户为RAM角色时,sub将根据角色扮演者
<RoleId:RoleSessionName>
生成,每个扮演者都有独立的sub。openid
aud
令牌接收者,OAuth应用ID。
openid
iss
令牌颁发者。取值为https://oauth.aliyun.com。
openid
iat
令牌颁发时间戳。
openid
type
登录用户类型。取值:
account:阿里云账号(主账号)。
user:RAM用户。
role:RAM角色。
profile
name
登录用户的显示名称。取值:
RAM用户:RAM用户的显示名称。
RAM角色:
<RoleName:RoleSessionName>
。
说明RAM用户和RAM角色请求时才会返回该参数。
profile
upn
RAM用户的登录名称。
说明RAM用户请求时才会返回该参数。
profile
login_name
阿里云账号(主账号)的登录名称。
说明阿里云账号(主账号)请求时才会返回该参数。
profile
aid
登录用户所属的阿里云账号(主账号)ID。
aliuid
uid
登录用户的ID。取值:
阿里云账号(主账号):阿里云账号(主账号)ID,与aid相同。
RAM用户:RAM用户ID。
RAM角色:RAM角色ID。
aliuid
返回示例
Header返回示例
{ "alg": "RS256", "kid": "JC9wxzrhqJ0gtaCEt2QLUfevEUIwltFhui4O1bh****" }
Body返回示例
为了阅读方便,以下展示的是未编码的身份令牌的返回示例。实际返回为编码后的身份令牌,更多信息,请参见示例二:验证身份令牌的JWT签名。
阿里云账号请求时的Body返回示例
{ "exp": 1517539523, "sub": "123456789012****", "aud": "4567890123456****", "iss": "https://oauth.aliyun.com", "iat": 1517535923, "type": "account", "login_name":"alice@example.com", //阿里云账号的登录名称 "aid": "123456789012****", //阿里云账号ID "uid": "123456789012****" //阿里云账号ID }
RAM用户请求时的Body返回示例
{ "exp": 1517539523, "sub": "123456789012****", "aud": "4567890123456****", "iss": "https://oauth.aliyun.com", "iat": 1517535923, "type": "user", "name": "alice", //RAM用户的显示名称 "upn": "alice@example.onaliyun.com", //RAM用户的登录名称 "aid": "123456789012****", //RAM用户所属的阿里云账号ID "uid": "234567890123****" //RAM用户ID }
RAM角色请求时的Body返回示例
{ "exp": 1517539523, "sub": "123456789012****", "aud": "4567890123456****", "iss": "https://oauth.alibabacloud.com", "iat": 1517535923, "type": "role", "name": "NetworkAdministrator:alice", //RAM角色的显示名称 "aid": "123456789012****", //RAM角色所属的阿里云账号ID "uid": "300800165472****" //RAM角色ID }
示例四:通过访问令牌和UserInfo接口获取用户信息
除了直接获取身份令牌,您也可以在获取访问令牌后通过调用UserInfo接口获取用户信息,该接口必须使用访问令牌才能访问,返回信息不编码。
OIDC场景下,即只有openid、aliuid和profile这几个范围的时候,也会返回访问令牌,此时的访问令牌只能用于调用UserInfo接口。
UserInfo接口请求地址:https://oauth.aliyun.com/v1/userinfo
。
请求示例如下:
GET v1/userinfo HTTP/1.1
Host: oauth.aliyun.com
Authorization: Bearer SlAV32hkKG
返回参数如下表所示:
参数名称 | 描述 | 需要的OAuth范围 |
sub | 唯一代表登录用户的字符串,但并不包含阿里云UID、用户名等信息。 | openid |
type | 登录用户类型。 | profile |
name | 登录用户的显示名称。 说明 RAM用户和RAM角色请求时才会返回该参数。 | profile |
upn | RAM用户的登录名称。 说明 RAM用户请求时才会返回该参数。 | profile |
login_name | 阿里云账号(主账号)的登录名称。 说明 阿里云账号(主账号)请求时才会返回该参数。 | profile |
aid | 登录用户所属的阿里云账号(主账号)ID。 | aliuid |
uid | 登录用户的ID。 | aliuid |
Body返回示例如下:
阿里云账号请求时的Body返回示例
HTTP/1.1 200 OK Content-Type: application/json { "sub": "123456789012****", "type": "account", "login_name":"alice@example.com", //阿里云账号的登录名称 "aid": "123456789012****", //阿里云账号ID "uid": "123456789012****" //阿里云账号ID }
RAM用户请求时的Body返回示例
HTTP/1.1 200 OK Content-Type: application/json { "sub": "123456789012****", "type": "user", "name": "alice", //RAM用户的显示名称 "upn": "alice@example.onaliyun.com", //RAM用户的登录名称 "aid": "123456789012****", //RAM用户所属的阿里云账号ID "uid": "234567890123****" //RAM用户ID }
RAM角色请求时的Body返回示例
HTTP/1.1 200 OK Content-Type: application/json { "sub": "123456789012****", "type": "role", "name": "NetworkAdministrator:alice", //RAM角色的显示名称 "aid": "123456789012****", //RAM角色所属的阿里云账号ID "uid": "300800165472****" //RAM角色ID }