文档

自研应用接入 SSO

1. 背景介绍

IDaaS 采用标准的 OIDC 协议授权码模式来支持常规企业自研应用接入。

如果您对接过微信登录,会发现对接 SSO 和对接微信扫码登录是一个道理。钉钉、微信等社交身份均采用 OAuth 协议实现扫码登录。IDaaS 采用的 OIDC 协议是 OAuth 协议的升级版。

说明

兼容 OAuth:OIDC(OpenID Connect) 1.0 协议在 OAuth2.0 协议之上建立了用户身份层,OIDC 协议也因此兼容 OAuth2.0 协议。OIDC 授权码模式和 OAuth2.0 授权码模式流程一致,区别是 OIDC 对用户信息端点进行了标准化,并在 Token 端点会返回用户的 ID Token。

授权码流程介绍

自研应用 SSO 对接采用 OIDC 授权码模式。

您的应用只需要完成与 IDaaS 之间的两个接口交互(授权端点、令牌端点),即可完成 SSO 的主体流程。

登录将由 IDaaS 完全托管,您的应用只需解析登录结果即可。

a

2. 对接 SSO

2.1. 创建自研(或 OIDC 协议)应用

您需要在 IDaaS 中创建一个自研应用或标准协议(OIDC)应用,并获取应用密钥。若您已经获取到,可以跳过这个步骤。

请管理员参考 3. 自研应用 完成应用创建。

在该应用的【通用配置】标签中,即可获取到 client_id 和 client_secret。这对密钥将用于后续接口请求。

秘钥

若希望对密钥进行管理或替换,请参考应用的 基本配置

2.2. 请求授权端点 Authorization Endpoint

下面步骤需要应用开发者处理。

在用户尝试访问您的应用时,应用需要判断当前是否有可用的已登录身份。

若用户需要登录,您需要向 IDaaS 发起授权登录的请求。您可以在【应用管理】【登录访问】标签中,下方的【应用配置信息】中获取应用的授权端点。

授权端点

请参照如下示例,在授权端点地址的基础上,拼装出完整的授权请求访问地址,并在浏览器中发起 302 跳转。

{{授权端点 Authorization Endpoint}}?
  client_id=app_***&
  redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F***&
  response_type=code&
  scope=openid&
  state=525f49cc-***

字段

必填

示例

说明

client_id

app_michs7r****6pye

上一步骤获取到的 client_id。

scope

openid email profile

自研应用默认配置为 openid email profile,意味着应用可获取到已登录账户的 ID、用户名、邮箱信息。若您使用 OIDC 标准模板应用,管理员还可在配置页选择 phone。scope 对应的字段范围,可见文档末尾说明。

response_type

code

此值固定为 code,代表采用授权码模式。

redirect_uri

http://localhost:3000/user/oauth2/aliyunidaas/callback

用户登录完成后,IDaaS 向应用返回登录结果的重定向地址。该地址应该为应用接收 IDaaS 参数的中继地址,并能接收授权码 code

state

525f49cc-87c4-4655-b79c-4c4f971b1ad1

state 是由应用生成的随机字符串,建议长度 32 位以上。该 state 值会在后续步骤返回给应用,应用届时应验证 state 值是否与发起传入的一致,以确保同一会话,以规避 XSRF 安全漏洞。非必填,但强烈建议填写。

2.3. 用户自助登录

若请求顺利,向应用授权端点的请求,会跳转到 IDaaS 登录页。

用户可以通过任意已配置的登录方式,完成身份验证。IDaaS 提供了多样的、不同安全级别的登录能力,包括钉钉扫码登录、短信登录等。详情参考:登录方式

登录成功后,浏览器会 302 跳转回应用指定的 redirect_uri,并在 URL 参数中携带 code 和 state 参数。

参考示例:

{{redirect_uri}}?
  code=CO***&
  state=525f49cc-***

字段

示例

说明

code

COE59pkCTm4A9nmowJUsfsfarGEaiShj3TuDc7NCzLCYu9

即授权码。在获取令牌的请求中使用。

state

525f49cc-87c4-4655-b79c-4c4f971b1ad1

应用接收到后,应确保与调用授权端点时传入的 state 一致。

2.4. 请求令牌端点 Token Endpoint

上一步接收到 code 授权码后,应用应使用获得的 code 向令牌端点(Token Endpoint)发起 POST 请求。

与上述授权端点一样,令牌端点也可以在【应用管理】【登录访问】标签下方的【应用配置信息】中获取到。

令牌端点

请求示例如下:

POST /v2/<instance_id>/<app_id>/oauth2/token HTTP/1.0
Host: eiam-api-cn-hangzhou.aliyuncs.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
  &code=n0esc3N*****5acc3f0ogp4
  &client_id=s6BhdR*****kqt3
  &client_secret=7Fjfp0ZBr1*****KtDRbnfVdmIw
  &redirect_uri=http%3A%2F%2Fwww.example.com%2Fsso%2Fcallback

字段

必填

示例

说明

grant_type

authorization_code

固定填写 authorization_code。

code

n0es***5acc3f0ogp4

即上一步中返回的授权码 code。

client_id

app_mihar***s3rj7r4e4

IDaaS 默认支持两种接口认证方式,以验证调用方可信。

client_secret_post 模式下,需要将 client_id, client_secret 作为 POST 参数传递给 IDaaS,进行调用方身份验证。请注意,若您通过 Postman 等工具验证,请确保您选择 form-data 格式。这是默认方式。

若您需要使用另一种 client_secret_basic 模式,请您按照规范传递即可,在此不多介绍。

client_secret

CSAuycr***vtqRozS1V1

同上。

redirect_uri

http%3A%2F%2Fwww.example.com%2Fsso%2Fcallback

即上一步使用的 redirect_uri,按照协议要求,必须重新传入以保障请求连续性。

响应结果如下:

{
  "token_type": "Bearer",
  "access_token": "ATM4SoVDqWgUJHLu3Bg6qF2hccE6cvjKXiKdiJ2Dc8RJZSbzpBDXPZK3gPhGxQs16s3s7MsZ46fEyiYTWG7EGFKi9uzGjRALaRLecPutBLzzQQRVUt6pbuarCbq5hFRje6bzsrW4jTehhCtZM5JneEfcSQ2ViSDVZGNNtMKAA6v7kTeubZrTaWNzosNMyzGXoD4rqPBwF9FsYqwACQ4aJrt9NnS3NpgDKoMtqEQs5TfDsCYMKYmp7Z73F2BJz89jzN1utEbnuj3HnvyRQPCismDiXjS8EPvoUZBrUBMhrnzYmMcT9KmzKoC12sQjDRQYqgPVxQyMKwQKwwHWXV7stEXnoSt524GW8HVrF3WRsM2N1Ykod1irCz7ZasSwk3ZS5mtn6fcSp8NH8",
  "expires_in": 1200,
  "expires_at": 1644843164,
  "id_token": "eyJraWQiOiJLRVkyVHkxcUw2dTIxTkdLbWNjdjNqd2ZkMm5kbWd0UVBuYWciLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX3V5dmVmb3RqbjdrcGJlamZteG9vczNydG1tIiwianRpIjoiand0X2FhYWFjN3h5aGNsYWM2YXFrZ3RqYXhzdGh3NXlvdG41ZDc3cG1raSIsImlzcyI6Imh0dHBzOi8vcHJlLWVpYW0tYXBpLWNuLWhhbmd6aG91LmFsaXl1bi1pbmMuY29tL3YyL2lkYWFzX2JxZ2xkdnpwcGEyYWw2aTZhYzVxemphcWpxL2FwcF9taHlsZ28zaWFpcmpxamR4NWVvcDZ1YWYzNC9vaWRjIiwiaWF0IjoxNjQ0ODQxOTY1LCJuYmYiOjE2NDQ4NDE5NjUsImV4cCI6MTY0NDg0MjI2NSwiYXVkIjoiYXBwX21oeWxnbzNpYWlyanFqZHg1ZW9wNnVhZjM0IiwiYXRfaGFzaCI6IlhIRWFHcE1vb005enZRWGFNekNORUEifQ.abebHwoSzOi92-QOKO_E38jyfxjzVLpRIK858UsOehe_GzBoKOEl1zQOSljBB7CwZCdQJpqI1rUxqQopwjvHRSfA-O4_cc4sXDpZYXodeVRXUiv1kYB1b4gZ-hStcE1eh_5jJj1dpoGPBsjTTHjp43EgDx1-8M-8ePF3zXZAfqxCjjroGgB9qXtSreRAIUh5ODViyHYRSAis7CNdP7jKG1dU1UNSGwXWNyRcgVaCqL05gCh0LhHrutMXDy8pcKzdXHQMMBaHF-rGkkGdlp4q9KqwjkpzakcWieRmPa2UUXLdQgK1Pgzc5F7mE-fvsvVfMYfh_JgRIadj-frOIRFChA"
}

至此,您的用户已经成功完成登录。您可以选择以下两种方式,进一步获取当前登录身份信息,并完成应用侧登录态的创建:

  1. 使用响应结果中的 id_token,经过验证后,直接拿到用户标识。

  2. 使用响应结果中的 access_token,调用 IDaaS 用户端点,获取当前已登录用户信息。

具体获取方式请参考下面章节。

注意

可以获取到的用户数据范围,由第一步授权端点请求中的 scope 参数指定。

2.5. 通过程序解析 id_token

id_token 是一个包含身份信息的签名令牌信息(JWT 格式)。IDaaS 签发的 id_token 中,包含着解码即明文可见的用户数据,以及签名 Signature。

为了方便理解,您可以将 id_token 完整内容,粘贴到 https://jwt.io 网站中,查看其包含的内容。

内容示例如下:

{
  "kid": "KEY2Ty1qL6u21NGKmccv3jwfd2ndmgtQPnag",
  "alg": "RS256"
}.{
  "sub": "user_uyvefotjn7kpbejfmxoos3rtmm",
  "jti": "jwt_aaaac7xyhclac6aqkgtjaxsthw5yotn5d77pmki",
  "iss": "https://pre-eiam-api-cn-hangzhou.aliyun-inc.com/v2/idaas_bqgldvzppa2al6i6ac5qzjaqjq/app_mhylgo3iairjqjdx5eop6uaf34/oidc",
  "iat": 1644841965,
  "nbf": 1644841965,
  "exp": 1644842265,
  "aud": "app_mhylgo3iairjqjdx5eop6uaf34",
  "at_hash": "XHEaGpMooM9zvQXaMzCNEA",
  "name": "testuser",
  "preferred_username": "testuser",
}.[Signature]

在使用其中内容进行应用登录前,您需要对签名 [Signature] 进行验证,以确保令牌是 IDaaS 签发,而非任何其他三方,以保障登录的安全性。这一安全性是必须的。

2.5.1. 获取验签公钥

在进行签名验证之前,需要首先获得 IDaaS 公开的验签公钥端点。

仍然在【应用管理】【登录访问】标签下方的【应用配置信息】中,我们可以看到应用的验签公钥端点。

验签端点

应用可通过访问这一端点,获取到当前的公钥信息。您也可以直接将此地址在浏览器中打开,即可展示公钥信息。

示例如下:

{
  "keys": [
    {
      "kty": "RSA",
      "e": "AQAB",
      "use": "sig",
      "kid": "KEYkYnc55GJjD6y7VeCTvT7So44RGDYdbfs",
      "n": "pXmYkIpy1vaNjTMclU86BQjfmDhjlqMAX8ySVvh9gO-nae4ayvG_aCRYQL3qGCpFLZYrG3Jjoa0ktCn8PTTRi-v4gP27T7u6bUy0GXTlh3eKE0v1LYB81nfqjF2uazlPwPR5yYOhhWcK-gMNByLfE3CnkDc1YGwA3dZmIz-ZjOCKy8xLaBuqjrvwn5tpMpAoYEEaH4jIm7unTdhbKEKspNR-UXKD8q9RppMh5Tn2sB6oPHlQANudJDgqSwEOevIrdmHU0Zqxrb9cscGH9hH0QjmYEu72yI8BVeliPo3jK6JIoqCIcj5K_t8BJlFQ9QLJ8_o9tmd3BFv5_LVsh4BKGw"
    }
  ]
}

接下来您即可以通过代码完成验签并获取 id_token 中的内容了。

2.5.2. 验签并登录

您可以通过 https://jwt.io/libraries 找到对应语言的工具,并在代码中使用工具对 id_token 进行验签与解析。

下面以 Java 库:org.bitbucket.b_c:jose4j 为例。

首先加入对应的 Maven 依赖:

<dependency>
  <groupId>org.bitbucket.b_c</groupId>
  <artifactId>jose4j</artifactId>
  <version>0.7.12</version>
</dependency>

代码示例如下:

import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;

public class IdTokenTest {

    public static void main(String[] args) throws Exception {
        String issuer = "https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/app_mkif4dwlpeh6dns4pxpzbasqmu/oidc";
        String appId = "app_mkif4*****pxpzbasqmu";
        // 请参照如下方式,设定解析用的应用公钥
        String jwkJson = "{\n"
                + "  \"keys\": [\n"
                + "    {\n"
                + "      \"kty\": \"RSA\",\n"
                + "      \"e\": \"AQAB\",\n"
                + "      \"use\": \"sig\",\n"
                + "      \"kid\": \"KEY2H82C2at57itnW4onT3p1ySjwH4nirjCk\",\n"
                + "      \"n\": \"w7Jl3fAUJp_9GuxV*****QsOA4lnXR5OD4kF4QbIeBiDiH8_MThrFi9k2MB6YMkSzf5JfIkpAS3JCqZ7k6Wooydp4pzaZNZAk3SGzdsa022RmAT"
                + "-Iayi4Yj6J9tSdTQCjwh2XkzzsIxA_Hla8rWiQ8Vhw1"
                +
                "-7QArgObfe67nSR7LxD55MFLxk9FU0*****RlGhrQGE_0LUuGWtCJG1r1e6aKquyswfxxAr3Rvj8QGIeJrG0R1Pv8m8d1_5OdULhB7149VqjM6D98WFjab0U2SNv0UlREZXTcS4p-2QNm_1egYRRpJEY_00FZqNSYsmErMGepYhO_61KoGqd8cphWQ\"\n"
                + "    }\n"
                + "  ]\n"
                + "}";
        String jwt = "eyJraWQiOiJLRVkySDgyQzJhdD*****uaXJqQ2siLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2Vy*****lmNjRjZjR3amFrbnBieGpjd3V1IiwianRpIjoiand0X2FhYWFkYWllYTc2eWg1cW0zcm11bnoyeGg0eHd5aTJzZHBoNjR6aSIsImlzcyI6Imh0dHBzOi8vZWlhbS1hcGktY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL3YyL2lkYWFzX3BhZHlybHV4M21waHJsc2V4NHVvbnlxaHh1L2FwcF9ta2lmNGR3bHBlaDZkbnM0cHhwemJhc3FtdS9vaWRjIiwiaWF0IjoxNjUzNjMwMDQxLCJuYmYiOjE2NTM2MzAwNDEsImV4cCI6MTY1MzYzMDM0MSwiYXVkIjoiYXBwX21raWY0ZHdscGVoNmRuczRweHB6YmFzcW11IiwibmFtZSI6InRlc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwidXBkYXRlZF9hdCI6MTY1MzYyODU5MH0.pAsUNB8OkdpIxJMZRfLJ7Pa31tsJyl44a1jVIlvdQxwOtPULAwrFxnB0X3eQx89hUGCdvYWl9FO9o-5kT7L-RER0wJYz9YNKqrVNBnaRwINRZyeYLRVurWMMzODQz-V0ULd9raM1M_i2f_SoWFs1gPFtYh_ijUARHISi7Q3q93ZfAuY8Lq2Nq07QunmDbosvioUd5wJG7WCxW5XXZYDUQe9p5IEYd1MSvnWuTOLbg7rKn0Vm4dNYGWjz1WuoAyCsc_QxOCqpmQ_2czoqPeN-SvPJAQ2CykLk7DSnGpABw1aNrjDidLS9Beqsga9VDCth86sk_0lyTZOaORtUrfVTtQ";

        JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson);
        JwtConsumer jwtConsumer = createJwtConsumer(jsonWebKeySet, issuer, appId);

        JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
        // 已经验签完成,打印输出 id_token 中包含的用户信息
        System.out.println(jwtClaims);
    }

    // 验签工具方法
    public static JwtConsumer createJwtConsumer(JsonWebKeySet jsonWebKeySet, String issuer, String appId) {
        final JwtConsumerBuilder jwtConsumerBuilder = new JwtConsumerBuilder();
        jwtConsumerBuilder.setExpectedIssuer(issuer);
        jwtConsumerBuilder.setRequireIssuedAt();
        jwtConsumerBuilder.setRequireExpirationTime();
        jwtConsumerBuilder.setAllowedClockSkewInSeconds(60);
        jwtConsumerBuilder.setExpectedAudience(appId);
        jwtConsumerBuilder.setVerificationKeyResolver((jws, nestingContext) -> {
            final String signKeyId = jws.getKeyIdHeaderValue();
            for (JsonWebKey jsonWebKey : jsonWebKeySet.getJsonWebKeys()) {
                if (signKeyId.equals(jsonWebKey.getKeyId())) {
                    return jsonWebKey.getKey();
                }
            }
            throw new RuntimeException("Cannot find verification key: " + signKeyId);
        });
        return jwtConsumerBuilder.build();
    }
}

输出示例如下:

JWT Claims Set:{sub=user_dt6kj6yf64cf4wjaknpbxjcwuu, 
                jti=jwt_aaaadaiea76yh5qm3rmunz2xh4xwyi2sdph64zi, 
                iss=https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/app_mkif4dwlpeh6dns4pxpzbasqmu/oidc, 
                iat=1653630041, 
                nbf=1653630041, 
                exp=1653630341, 
                aud=app_mkif4dwlpeh6dns4pxpzbasqmu, 
                name=test, 
                preferred_username=test, 
                updated_at=1653628590
               }

由此获取到 IDaaS 中已登录身份信息,应用可用其顺利登录。

2.6 通过UserInfo端点获取用户信息

除了可以通过解析 id_token 获取用户信息外,还可以通过 UserInfo 端点(用户信息端点)获取用户信息。

仍然在【应用管理】【登录访问】标签下方的【应用配置信息】中,我们可以看到应用的验签公钥端点。

userinfo

UserInfo请求遵循标准 RFC6750,请求示例如下:

GET /v2/<instance_id>/<app_id>/oauth2/userinfo HTTP/1.0
Host: eiam-api-cn-hangzhou.aliyuncs.com
Authorization: Bearer <AccessToken>

返回参数示例:
{
    "sub": "user_dt6kj6yf64cf4wjaknpbxjcwuu",
    "name": "test",
    "preferred_username": "test",
    "updated_at": 1653899948
}

说明

UserInfo 端点返回的业务字符与 id_token 中的字段是保持一致的,即在 “扩展 id_token” 中配置的字段也会在 UserInfo 端点中返回。

3. 其他高级设置

若您对 OIDC 协议有深入了解,您可能会用到下列概念或能力,供参考。

3.1. OIDC Discovery 应用发现端点说明

OIDC 应用的 issuer 是对令牌发行方(即 IDaaS)的唯一标识,格式如下:

https://<idaas-api-domain>/v2/<instance_id>/<application_id>/oidc

尖括号中的参数如下:

字段

说明

示例

idaas-api-domain

用户门户地址

https://nfaaacn.aliyunidaas.com

instance_id

实例 ID

idaas_maaaaaaaaaaar2ed22e6m

application_id

应用 ID

app_maaaaaaaaaaaaaaajy6rbau

IDaaS 支持 OpenID Connect Discovery 1.0 标准,在 issuer 后再加上 /.well-known/openid-configuration 就是该应用的 OIDC 发现端点地址。

通过请求发现端点,您可以自动发现如下端点信息。所有的请求端点都可以直接从【应用配置信息】中获取。

端点

说明

authorization_endpoint

授权端点

device_authorization_endpoint

设备模式 需要标准OIDC应用支持该功能,自研应用暂时不支持设备码流登录

token_endpoint

令牌端点

revocation_endpoint

令牌吊销端点

userinfo_endpoint

用户信息端点

jwks_uri

JWK公钥端点

3.2. Scope 与字段权限的对应关系

OIDC 中 id_token 中包含的用户信息与 scope 的对应关系如下:

字段

scope

说明

sub

openid

用户的 userId

jti

openid

JWT 令牌ID,辅助字段

iss

openid

JWT 签发的 issuer,辅助字段

iat

openid

JWT 签发时间,辅助字段

nbf

openid

JWT 令牌有效开始时间,辅助字段

exp

openid

JWT 令牌过期时间,辅助字段

aud

openid

即应用的 ClientID,辅助字段

at_hash

openid

AccessToken 哈希值,辅助字段

phone_number

phone

电话号码,比如 +86 130 1234 5678

phone_number_verified

phone

电话号码是否被验证过,目前默认电话号码是已验证

email

email

电子邮箱,比如 al***@example.com

email_verified

email

电子邮箱是否被验证过,目前默认电子邮箱是已验证

name

profile

用户显示名

preferred_username

profile

用户的 username

updated_at

profile

用户资料最后更新时间

3.3. 令牌端点支持的认证方式

根据 OIDC 协议指明,IDaaS 提供灵活性,允许以下 4 种不同方式进行身份验证。

在发现端点中返回的字段 token_endpoint_auth_methods_supported 指定了支持的认证方法。

取值

说明

none

用于 Public 客户端,通过 none 认证方式认证时 grant_type 不能是 client_credentials

client_secret_basic

按规范 RFC 6749 - The OAuth 2.0 Authorization Framework 实现

client_secret_post

按规范 RFC 6749 - The OAuth 2.0 Authorization Framework 实现

client_secret_jwt

按规范 OpenID Connect Core 1.0 实现

上一步接收到 code 授权码,并验证请求合法(验证 state 与发起请求传入的一致)后,应用的后端服务,应使用获得的 code 向令牌端点(Token Endpoint)发起 POST 请求,请求示例如下:

client_secret_basic 为例,令牌端点请求样例为:

POST /token HTTP/1.0
Host: api.aliyunidaas.com
Authorization: Basic YXBwX21pY2hzN3I0*******cHllOkNTKioqKioq

grant_type=authorization_code&
code=COE59pkCTm4J*******arGEaiShj7NCzLCYu9

更多说明参看 OIDC Core 1.0 规范。

3.4. 应用 Client Secret 轮转

请参考 基本配置 中密钥轮转章节说明。

相关标准

  • 本页导读 (0)
文档反馈