全部产品
Search
文档中心

OpenID Connect 认证

更新时间: 2018-08-10

OpenID Connect 是一套基于 OAuth 2.0 协议的轻量认证级规范,提供通过 API 进行身份交互的框架。较 OAuth 而言, OpenID Connect 方式除了认证请求之外,还标明请求的用户身份。

API 网关依据 OpenID Connect 的标准,提供两种认证方式:

  • OpenID Connect

    标准的OpenID Connect模式,调用API时先通过用户名密码获取Token,之后的每次API请求都通过Token来验证。

  • OpenID Connect & 阿里云APP

    在OpenID Connect基础上增加Appkey认证, 会同时验证API请求中的 Appkey和Token(Token 由 API 提供者的系统颁发,网关颁发 Appkey)。

  • 两种OpenID Connect认证区别

    OpenID Connect & 阿里云APP需要认证APPkey,OpenID Connect则不需要。无论哪种模式,都推荐具有登陆态的APP、服务端或者前端程序来使用。

    使用这两种认证方式建议:配置好流量控制,避免恶意用户进行暴力破解用户名/密码。

  • 使用标准OpenID Connect将无法使用的功能

    • APP鉴权,无法使用。
    • APP级流量控制,无法配置/无法生效。
    • 阿里云用户级流量控制,无法配置/无法生效。

实现原理

使用 OpenID Connect认证,需要您在 API 网关配置获取授权 API业务 API 两种类型的API 。

APi

API调用

  • 获取授权API:用于您的客户端获取Token 。配置这个API时,您需要告知API网关您Token对应的Key和解析Token使用的公钥。
  • 业务类接口,是您实际的业务接口,比如获取用户息、进行某个操作等。配置这类API时,你需要告知API网关你请求中表示Token的参数名称。当客户端调用这类API的请求到达API网关后,API网关自动验证这个请求的Appkey和Token是否合法。

认证方式

  1. 客户端调用 “获取授权 API” 获取Token的流程

    1. 客户端使用认证信息来获取Token,
      • OpenID Connect:需要使用用户的“用户名/密码”调用“获取授权” API 获取Token。。
      • OpenID Connect & 阿里云APP:“Appkey 签名”+“用户名/密码” 调用 “获取授权” API 获取Token。
      • 调用API请参照:调用API
    2. API 网关收到请求后,OpenID Connect & 阿里云APP模式(OpenID Connect不需要)认证您的 Appkey后,调用后端服务的账号系统认证您传递的 “用户名/密码”

    3. 后端服务认证认证用户名/密码,通过后返回 Token 给您,您可凭 Token 来调用 “业务 API”

  2. 客户端调用业务类 API ,来实现业务功能

    1. 客户端使用 “获取授权 API” 得到的 Token签名后的 Appkey 来调用 “业务API”。(调用API请参照:调用API

    2. API 网关认证、解析 Token 的内容,并将 Token 中包含的用户信息传递给后端。

    3. 在此阶段的操作中,API 的提供者需要事先进行如下操作:

      1. 开放账号系统,允许 API 网关对请求中 用户名/密码 进行验证,并依据网关提供的加密方式,颁发 Token。详细内容请参照下文 如何实现 AS 模块
      2. 在 API 网关定义 API。详细内容参照下文 在 API 网关配置 API
        注意用户名/密码 是极为敏感的信息,在网络中明文传输存在风险,建议在传输前对用户名密码再次加密,并使用 HTTPS 协议传输。

实现方案简介

实现方案分为两个重要的部分:

1. Authorization server(AS):认证服务器,负责生成 id_Token 并管理公钥私钥对。

这一步需要您自行实现。实现方法,请参照下文 在 API 网关配置 API
u_p

参考上图,流程简述如下:

  1. Consumer(调用者)向API网关发送获取 id_token 认证请求,比如:通过用户名和密码(U+P)的方式。
  2. API 网关透传该请求到 AS。
  3. AS 向 Provider(服务提供方)发送认证用户信息请求。
  4. Provider 响应认证结果,若失败则直接响应错误信息。
  5. 认证结果成功,AS 生成 id_token,id_token 中包含了 User 信息(可扩展,也可包含其他必要信息)。
  6. API 网关将 AS 返回的 id_token 响应给 Consumer。

    说明:AS 不用必须是单独部署的应用,完全可以集成在 Provider 中,在整个体系中担任 id_token 生产者角色,所生成的 id_token 必须符合 OIDC(1.0版本)协议中的 规范

2. Resource server(RS):资源服务器,负责校验 id_token,并解析出相应的信息。

此部分由网关来完成。因为 API 网关目前已经集成了 RS 功能,服务提供方只需要按照相应的加密规则生成 id_token 即可。
rs

参考上图,流程简述如下:

  1. Consumer 用带有 id_token 的参数去请求 API 网关。
  2. API 网关会保存校验所使用的公钥,验证并解析 id_token 获取其中的 User 信息传给 Provider,若验证失败则直接返回错误信息。
  3. Provider 处理请求并返回结果给 API 网关。
  4. API 网关透传 Provider 响应的结果给 Consumer。

说明:RS 在整个体系中担任 id_token 消费者角色,只有 id_token 校验通过,才能将请求转发给 Provider。

如何实现 AS 模块

AS 中使用 OIDC 生成 id_token 的说明

  • id_token,也叫 ID Token,是在 OIDC 协议中定义的一种令牌,详细内容参见 OpenID Connect Core 1.0
  • id_token 生成需要 KeyPair, keyId 与 Claims (有关Claims更多信息请访问 ID_Token)。

KeyId 说明

KeyId 必须保证唯一,比如使用 UUID 生成的长度至少32位的随机字符串,可以全为数字或数字+字母。
参考示例(JAVA)

  1. String keyId = UUID.randomUUID().toString().replaceAll("-", "");

  1. String keyId = String.valueOf(UUID.randomUUID().getMostSignificantBits()) + String.valueOf(UUID.randomUUID().getMostSignificantBits());

KeyPair 说明

KeyPair 是一个基于 PKI 体系的非对称算法的公私钥组合,每一对包括公钥(publicKey)与私钥(privateKey); 公钥放置在 RS 中,在校验(verify)时使用,私钥放置在 AS 中,在生成 id_token 时做数字签名使用;
KeyPair 使用 RSA SHA256 加密算法,为保证足够安全其加密的位数为2048;
AS 中使用的 KeyPair 均为 JSON 格式的数据,一个示例如下:
publicKey:

  1. {"kty":"RSA","kid":"67174182967979709913950471789226181721","alg":"ES256","n":"oH5WunqaqIopfOFBz9RfBVVIIcmk0WDJagAcROKFiLJScQ8N\_nrexgbCMlu-dSCUWq7XMnp1ZSqw-XBS2-XEy4W4l2Q7rx3qDWY0cP8pY83hqxTZ6-8GErJm\_0yOzR4WO4plIVVWt96-mxn3ZgK8kmaeotkS0zS0pYMb4EEOxFFnGFqjCThuO2pimF0imxiEWw5WCdREz1v8RW72WdEfLpTLJEOpP1FsFyG3OIDbTYOqowD1YQEf5Nk2TqN\_7pYrGRKsK3BPpw4s9aXHbGrpwsCRwYbKYbmeJst8MQ4AgcorE3NPmp-E6RxA5jLQ4axXrwC0T458LIVhypWhDqejUw","e":"AQAB"}
privateKey:
  1. {"kty":"RSA","kid":"67174182967979709913950471789226181721","alg":"ES256","n":"oH5WunqaqIopfOFBz9RfBVVIIcmk0WDJagAcROKFiLJScQ8N\_nrexgbCMlu-dSCUWq7XMnp1ZSqw-XBS2-XEy4W4l2Q7rx3qDWY0cP8pY83hqxTZ6-8GErJm\_0yOzR4WO4plIVVWt96-mxn3ZgK8kmaeotkS0zS0pYMb4EEOxFFnGFqjCThuO2pimF0imxiEWw5WCdREz1v8RW72WdEfLpTLJEOpP1FsFyG3OIDbTYOqowD1YQEf5Nk2TqN\_7pYrGRKsK3BPpw4s9aXHbGrpwsCRwYbKYbmeJst8MQ4AgcorE3NPmp-E6RxA5jLQ4axXrwC0T458LIVhypWhDqejUw","e":"AQAB","d":"aQsHnLnOK-1xxghw2KP5JTZyJZsiwt-ENFqqJfPUzmlYSCNAV4T39chKpkch2utd7hRtSN6Zo4NTnY8EzGQQb9yvunaiEbWUkPyJ6kM3RdlkkGLvVtp0sRwPCZ2EAYBlsMad9jkyrtmdC0rtf9jerzt3LMLC7XWbnpC3WAl8rsRDR1CGs\_-u4sfZfttsaUbJDD9hD0q4NfLDCVOZoQ\_8wkZxyWDAQGCe6GcCbu6N81fTp2CSVbiBj7DST\_4x2NYUA2KG8vyZYcwviNTxQzk4iPfdN2YQz\_9aMTZmmhVUGlmTvAjE5ebBqcqKAS0NfhOQHg2uR46eBKBy\_OyVOLohsQ","p":"8Tdo3DCs-0t9JMtM0lYqPRP4wYJs37Rv6S-ygRui2MI\_hadTY9I2A199JMYw7Fjke\_wa3gqJLa98pbybdLWkrOxXbKEkwE4uc4-fuNjLbUTC5tqdM5-nXmpL887uREVYnk8FUzvWeXYTCNCb7OLw5l8yPJ1tR8aNcd0fJNDKh98","q":"qlRrGSTsZzBkDgDi1xlCoYvoM76cbmxrCUK-mc\_kBRHfMjlHosxFUnAbxqIBE4eAJEKVfIJLQrHFvIDjQb3kM9ylmwMCu9f8u9DHrT8J7LSDlLqDaXuiM2oiKtW3bAaBPuiR7sVMFcuB5baCebHU487YymJCBTfeCZtFdi6c4w0","dp":"gVCROKonsjiQCG-s6X4j-saAL016jJsw-7QEYE6uiMHqR\_6iJ\_uD1V8Vuec-RxaItyc6SBsh24oeqsNoG7Ndaw7w912UVDwVjwJKQFCJDjU0v4oniItosKcPvM8M0TDUB1qZojuMCWWRYsJjNSWcvAQA7JoBAd-h6I8AqT39tcU","dq":"BckMQjRg2zhnjZo2Gjw\_aSFJZ8iHo7CHCi98LdlD03BB9oC\_kCYEDMLGDr8d7j3h-llQnoQGbmN\_ZeGy1l7Oy3wpG9TEWQEDEpYK0jWb7rBK79hN8l1CqyBlvLK5oi-uYCaiHkwRQ4RACz9huyRxKLOz5VvlBixZnFXrzBHVPlk","qi":"M5NCVjSegf\_KP8kQLAudXUZi\_6X8T-owtsG\_gB9xYVGnCsbHW8gccRocOY1Xa0KMotTWJl1AskCu-TZhOJmrdeGpvkdulwmbIcnjA\_Fgflp4lAj4TCWmtRI6982hnC3XP2e-nf\_z2XsPNiuOactY7W042D\_cajyyX\_tBEJaGOXM"}

生成 KeyPair 参考示例(JAVA)

  1. import java.security.PrivateKey;
  2.  
  3. import org.jose4j.json.JsonUtil;
  4. import org.jose4j.jwk.RsaJsonWebKey;
  5. import org.jose4j.jwk.RsaJwkGenerator;
  6. import org.jose4j.jws.AlgorithmIdentifiers;
  7. import org.jose4j.jws.JsonWebSignature;
  8. import org.jose4j.jwt.JwtClaims;
  9. import org.jose4j.jwt.NumericDate;
  10. import org.jose4j.lang.JoseException;
  1. String keyId = UUID.randomUUID().toString().replaceAll("-", "");
  2. RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048);
  3. jwk.setKeyId(keyId);
  4. jwk.setAlgorithm(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
  5. String publicKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.PUBLIC_ONLY);
  6. String privateKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);

生成 id_token 参考步骤

  1. 通过 OIDC 协议中定义的 Claims 属性(aud, sub, exp, iat, iss)与其属性值,生成 Claims(全称 JwtClaims)

    示例代码(JAVA)

    1. JwtClaims claims = new JwtClaims();
    2. claims.setGeneratedJwtId();
    3. claims.setIssuedAtToNow();
    4. //expire time
    5. NumericDate date = NumericDate.now();
    6. date.addSeconds(120);
    7. claims.setExpirationTime(date);
    8. claims.setNotBeforeMinutesInThePast(1);
    9. claims.setSubject("YOUR_SUBJECT");
    10. claims.setAudience("YOUR_AUDIENCE");
    11. //添加自定义参数
    12. claims.setClaim(key, value);
  2. 通过 keyId, Claims, privateKey 与使用的数字签名算法 (RSA SHA256 )生成 JWS( Json Web Signature)

    示例代码(JAVA)

    1. JsonWebSignature jws = new JsonWebSignature();
    2. jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
    3. jws.setKeyIdHeaderValue(keyId);
    4. jws.setPayload(claims.toJson());
    5. PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyText)).getPrivateKey();
    6. jws.setKey(privateKey);
  3. 通过 JWS 获取 id_token 值

    示例代码(JAVA)

    1. String idToken = jws.getCompactSerialization();

    一个生成的 id_token 示例:

    1. eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg4NDgzNzI3NTU2OTI5MzI2NzAzMzA5OTA0MzUxMTg1ODE1NDg5In0.eyJ1c2VySWQiOiIzMzcwMTU0NDA2ODI1OTY4NjI3IiwidGFnTmFtZSI6ImNvbmFuVGVzdCIsImV4cCI6MTQ4MDU5Njg3OSwiYXVkIjoiQWxpX0FQSV9Vc2VyIiwianRpIjoiTm9DMFVVeW5xV0N0RUFEVjNoeEIydyIsImlhdCI6MTQ4MDU5MzI3OSwibmJmIjoxNDgwNTkzMjE5LCJzdWIiOiJ7ZGF0YU1hcD0ne3VzZXJJZD0zMzcwMTU0NDA2ODI1OTY4NjI3fScsIHN0YXR1c0NvZGU9JzAnLCBlcnJvcnM9J1tdJ30ifQ.V3rU2VCziSt6uTgdCktYRsIwkMEMsO_jUHNCCIW_Sp4qQ5ExjtwNt9h9mTGKFRujk2z1E0k36smWf9PbNGTZTWmSYN8rvcQqdsupcC6LU9r8jreA1Rw1CmmeWY4HsfBfeInr1wCFrEfZl6_QOtf3raKSK9AowhzEsnYRKAYuc297gmV8qlQdevAwU75qtg8j8ii3hZpJqTX67EteNCHZfhXn8wJjckl5sHz2xPPyMqj8CGRQ1wrZEHjUmNPw-unrUkt6neM0UrSqcjlrQ25L8PEL2TNs7nGVdl6iS7Nasbj8fsERMKcZbP2RFzOZfKJuaivD306cJIpQwxfS1u2bew

在 API 网关配置 API

  1. API 编辑功能中,基本信息栏目中安全认证增加 OpenID Connect 选项,这种方式同时也包括了 阿里云APP 认证方式,也就是说只有被授权的 APP 才能调用这个 API。

    api

  2. 选择 OpenID Connect 这种认证方式后,接下来要选择 OpenID Connect 模式,有两个选项:
    API-2

    1. 获取授权 API:您用来换取 Token 的 API,比如:通过 U+P 换取 Token。
    2. 业务 API:也就是服务提供商提供服务的 API,调用者会把之前获取到的 Token 作为入参进行调用。
      OpenID Connect 认证方式主要包含的就是以上这两种 API,下文将分别说明这两种API 是如何配置的。
  3. 获取授权 API 还需要配置 KeyId 和公钥,见下图:
    pai-3

    KeyId:公钥私钥对 对应的一个唯一 Id,由 As 模块负责生成的;示例:

    1. 88483727556929326703309904351185815489
    公钥:负责验证和解析 Token,由 As 模块负责生成的,示例:

    1. {"kty":"RSA","kid":"88483727556929326703309904351185815489","alg":"ES256","n":"ie0IKvKLd7Y3izHcZemdDsVVXg5QtWtGF7XEkILnn66R2\_3a30DikqV409OVL7Hv0ElACgCaBLEgZeGHTcdLE1xxDTna8MMBnBNuMVghvFERCKh8uzpxlQsfcnFd5IFdJWj1x5Tscetrow6lA3h5zYx0rF5TkZzC4DclxgDmITRam0dsHBxr3uk9m9YYBz2mX0ehjY0px7vIo7hZH2J3gODEPorIZkk3x8GPdlaA4P9OFAO4au9-zcVQop9vLirxdwDedk2p-F9GP6UiQC9V2LTWqkVw\_oPBf9Rlh8Qdi19jA8SeCfzAxJZYlbOTK8dYAFAVEFsvXCFvdaxQefwWFw","e":"AQAB"}

    设置完这些,后面的设置就和之前普通的 API 一样了,在此就不赘述了。

    不管是创建 API 还是修改 API,所设置的 KeyId 和公钥都是在 API 发布之后才会生效。

  4. 业务 API 需要配置 Token所对应的参数名称

    api-4

    1. 如上图,Token 所对应的参数名称:就是调用者调用 API 传入 id_token 所使用的参数名,API 网关会识别这个参数值,校验并解析。

    2. 然后在入参定义中,必须要定义一个对应参数,否则系统会出现错误提示,见下图。api-5

    3. 设置自定义系统参数:业务 API在 定义 API 后端服务 标签中会开放配置自定义系统参数,举个例子,见下图:
      api-6比如:AS 生成的 id_token 中包含了调用者的 userId,那么如果按照上图这样配置的话,就会把从调用者传过来的 id_token 中解析出来的 userId 传给服务提供方,配置方式和系统参数类似。
      除了以上3处,定义 API 其他配置和前文一样,也就不再赘述了。

    以上就是 第三方账号认证 OpenID Connect 在 API 网关配置的全部内容,供您参考!