OAuth2

最后更新:2021-12-01

1. 应用介绍

1.1. OAuth2.0是什么?

OAuth(Open Authorization,开放授权)是一个开放标准的授权协议,允许用户授权第三方应用访问他们存储在资源服务上受保护的信息,而不需要将用户名和密码提供给第三方应用,解耦了认证和授权。OAuth2.0是OAuth协议的延续版本,更加安全,更易于实现,但不向后兼容OAuth1.0,即完全废止了OAuth1.0,由于OAuth1.0协议复杂,安全性不高,已被OAuth2.0取代。它主要是一个授权协议,允许软件应用代表(而不是充当)资源拥有者去访问资源拥有者的资源。应用向资源拥有者请求授权,然后得到令牌(Token),并用它来访问资源。
​ ​ OAuth作为一种国际标准,目前传播广泛并被持续采用,并在协议规范【RFC 6749】详细定义OAuth2.0的实现细节。

OAuth 2.0 规定了四种获得令牌的流程,分别是授权码模式(authorization_code)、简化模式(implicit)、密码模式(password)、客户端模式(client_credentials), 可以选择实际情况选择最适合的一种,向第三方应用颁发令牌。比如授权码模式, 客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。

1.2. IDaaS平台OAuth2概述

IDaaS平台上的OAuth2应用基于标准OAuth2协议
image.png
支持简化模式(implicit)和 授权码模式(authorization-code)以及 PKCE (属于授权码模式的一个扩展)
image.png

其中,使用授权码模式能实现从IDaaS到您业务应用单点登录功能。

以下主要包含以下内容:

  • 时序说明 - 以OAuth2协议授权码模式接入IDaaS的简单时序图说明,以及交互参数

  • 主要流程 - OAuth2.0模板使用主要流程

  • 操作步骤 - 从新建开始配置一个OAuth2应用,以及如何在客户端中开发,包含具体API请求、响应和错误提示,详细如下

    • 如何获取用户令牌

    • 如何获取用户信息userinfo

    • 如何刷新用户令牌

  • 常见QA - 常见问题以及其对策

2. 如何配置

2.1. 时序说明

2.1.1. 场景:SP发起单点登录时序

OAuth 2.0的草案是在2010年5月初在IETF发布的。OAuth 2.0是OAuth协议的下一版本,但不向后兼容OAuth 1.0。 OAuth 2.0关注客户端开发者的简易性,同时为Web应用,PC应用和手机,和IOT设备提供专门的认证流程。规范在IETF OAuth工作组的主导下,OAuth标准于2010年末完成。

OAuth2是一个授权协议, 主要用来作为API的保护, 我们称之为STS(安全令牌服务, Security Token Service)。 但是在某些情况下, 也可以被用来实现WEB SSO单点登录。一般的流程是用户把发起页面的URL和state参数关联上, 并保存在SP本地, 用户登录后, 可以获取一个Code, 利用Code拿到AT(Access Token) 后, 可以利用这个AT获取用户信息userinfo, 进而从state 中, 获取到对应的原始URL, 并跳转到这个URL, 从而实现登录到一个业务应用SP的效果。 下图详细描述了这个SSO过程。

详细时序图(以授权码模式为例):


说明:
第[5]步参数要求

  • response_type:必选、值固定为”code”

  • client_id:必选、第三方应用的标识ID

  • state:推荐、Client提供的一个字符串,服务器会原样返回给Client,它既能防止CSRF、XSRF, 同时也可以用来对应SP初始发起的状态。

  • redirect_uri:必选、授权成功后的重定向地址

  • scope:可选、表示授权范围

  • prompt:可选

第[6]步校验内容

  • a.client_id是否合法

  • b.prompt:

    1. 若应用请求IDP时不带prompt参数,则逻辑为用户没登录就跳转到登录页

    2. 若应用请求IDP时带参数prompt=none,则默认用户已经登录验证,如果IDP发现用户未登录验证,则直接报interaction_required错误

    3. 若应用请求IDP时带参数prompt=login,则不论用户是否已经登录认证,都重新走一次认证流程

第[11]步返回参数

  • 跳转到[5]中指定redirect_uri,并返回:code:授权码 state:步骤[5]中客户端提供的state参数原样返回

第[13]步校验参数

  • state是否和自己发送出的一致

第[14]步请求参数

  • grant_type:必选、固定值”authorization_code”

  • code:必选、Authorization Response中响应的code

  • redirect_uri:必选、必须和Authorization Request中提供的redirect_uri相同

  • client_id:必选、必须和AuthorizationRequest中提供的client_id相同

  • client secret: client的secret,用于授权服务器校验client的合法身份

第[15]步校验参数

  • a.client_id、client_secret(若有)是否合法

  • b.redirect_uri是否和步骤[A]中的redirect_uri一致

  • c.code是否合法:

  • 是否过期

  • 是否被重复使用,若是就视为一次攻击,加入日志审计,并将之前为code生成的access token撤销

  • 比较code和应用的client id是否匹配

  • d.server必须在http server头部返回:Cache-Control:no-store and Pragma:no-cache,确保client不会被缓存

第[16]步返回参数

  • access_token:访问令牌

  • refresh_token:刷新令牌

  • expires in:过期时间

第[19]步完成单点登录

  • 完成了这一步,就获取到access_token和用户信息,可以展示当前登录用户信息,基于此保存的session会话,用户可以不用再频繁登录,实现点击图标即可跳转应用的过程。

2.2. 主要流程

Step1 创建OAuth2应用,基于OAuth2模板快速创建应用
Step2 授权OAuth2应用,对OAuth2应用授予访问权限
Step3 获取应用信息,基于配置应用信息主要为获取授权码Code
Step4 访问授权URL获取Code,通过相关应用配置,跳转应用地址
Step5 完成应用侧的开发/配置,就可以实现业务应用单点登录功能

2.3. 操作步骤

2.3.1. Step1 创建OAuth2应用:

1、首先以IT管理员账号登录云盾IDaaS管理平台。具体操作请参考 IT管理员指南-登录 。
2、点击左侧导航栏应用 > 添加应用 选择右侧OAuthimage.png
3、选择OAuth2应用模板点击
添加应用

image.png

4、Redirect URI : 填写需要使用OAuth2单点登录应用的URL
GrantType : 选择authorization_code
其他参数默认即可,有需要也可按照实际需要修改


2.3.2. Step2 OAuth2应用授权

应用授权:选择应用(搜索应用)、选择组织机构(搜索组织机构)、勾选授权即可
image.png

2.3.3. Step3 获取应用信息

点击左侧导航栏应用 > 应用列表 查看 OAuth2 应用详情, 获得 Client Id、Client Secret、Authorize URL.
image.png
image.png


2.3.4. Step4 访问授权URL获取Code

参考SP发起单点登录时序:认证成功生成code
应用通过一个IDP登录按钮等或其它方式, 触发浏览器打开 AuthorizeURL,使用授权的账户进行登录,登录成功后跳转到回调地址redirect_uri,并把Code参数一同转发过去。
image.png

2.3.5. Step5 完成应用侧的开发/配置

2.3.5.1. 、利用Code从服务器获取AT(Access Token)

参考SP发起单点登录时序:请求access_token
无论是JAVA, PHP, 还是.NET应用, 接下来要做的是,应用通过URL参数拿到这个Code 后, 紧接着构建一个应用Token 换AT(Access Token)的过程。
Request URI:
https://{IDaaS_server}/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}
IDaaS_server:为IDaaS服务部署访问的host地址
注: OAuth支持多种grant_type 这里使用的是authorization_code模式。

  • 接口说明:获得 access_token

  • 请求方式:POST

  • 请求参数

参数

类型

是否必选

示例值

描述

code

String

vuQ3n6

用户登录成功后回调传递的code值

client_id

String

oauth2 client_id

OAuth2 client_id

client_secret

String

oauth2 client_secret

OAuth2 client_secret

redirect_uri

String

http://example.com

重定向 url

  • 返回参数示例:

{
    "access_token": "eyJhbGc1NiIs...",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 7199,
    "scope": "read",
    "jti": "956901cd-5669-411f"
}

参数

类型

示例值

描述

access_token

String

eyJhbGc1NiIs…

Access Token

token_type

String

bearer

Token 类型

refresh_token

String

eyJhbGciOi…

刷新token

expires_in

String

7199

Access Token 过期时间

scope

String

read

申请的权限范围

jti

String

956901cd-5669-411f

当令牌是jwt格式时,该值表示令牌的id

  • 错误码说明

HttpCode

错误码

错误信息

描述

400

invalid_grant

Invalid authorization code: “code”.

无效的授权码

400

invalid_grant

Redirect URI mismatch.

重定向 URI 不匹配

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.

发生未知错误


{code}需要替换为授权应答Authorization Response中提取到的 code 参数的值。
注意 Code 的值只能用一次
{client_id}、{client_secret}需要替换为认证成功生成code中获得的值
{redirect_uri} 需要替换为302重定向到IDP进行认证授权添加 OAuth2 应用时输入的跳转值
以上完成后你将获得AT(Access Token),此AT将作为你访问的凭证。

2.3.5.2. 、获取用户信息userinfo

参考SP发起单点登录时序:** 应用请求userinfo(携带access_token)**
在获取到AT(Access Token)后,应用可以接着向IDaaS平台发送进一步的请求, 以获取到用户信息,实现登录到一个业务应用SP的效果。
发送GET请求到https://{IDaaS_server}/api/bff/v1.2/oauth2/userinfo?access_token={access_token}
{access_token}替换为前一步获取到的AT(Access Token)
从返回参数即可获取userinfo信息
Request URI: https://{IDaaS_server}/api/bff/v1.2/oauth2/userinfo

  • 接口说明:获取用户详细信息

  • 请求方式:GET

  • 请求参数 ​

参数

类型

是否必选

示例值

描述

access_token

String

eyJhbGc1NiIs…

Access Token

  • 返回参数响应示例


{
    "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

子编号

└ouid

String

2079225187122667069

父组织ID

└nickname

String

test

昵称

└phone_number

String

11136618971

手机号

└ou_name

String

研发部

父组织名称

└email

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.

发生未知错误

这样, 用户登录成功后, 浏览器有了主会话, 一个SP应用利用它获取一个令牌AT(AccessToken),应用拿到AT令牌后去IDaaS认证中心校验令牌是否有效,同时到/userinfo接口去拉取更多的用户信息, 获取到具体的子账户UserId, 有了UserId 就可以创建SP的子会话。 从而在子会话有效期都不用再登录,实现从IDaaS单点登录到应用的全过程。

2.3.5.3. 获取应用子账户信息

Request URI: https://{IDaaS_server}/api/bff/v1.2/oauth2/userinfo/sub

  • 接口说明:获取应用子账户信息

  • 请求方式:GET

  • 请求参数 ​

参数

类型

是否必选

示例值

描述

access_token

String

eyJhbGc1NiIs…

Access Token

client_id

String

xxxxxx

应用client_id

  • 返回参数响应示例


{
  "success": true,
  "code": "200",
  "message": null,
  "requestId": "1658299120963$c975d578-e80c-1f99-8a38-42e08576df05",
  "data": {
    "sub_accounts": [
      "fff"
    ]
  }
}
  • 参数说明


参数

类型

示例值

描述

success

boolean

true

是否成功

code

String

200

状态码

message

String

null

返回消息

requestId

String

B3776BB1-930F-4581-B4C3-18F2D7D136CA

请求ID

data

Object

响应数据

└sub_accounts

Arrays

子账户列表

  • 错误码说明


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.

发生未知错误

2.3.5.4. 获取应用账户扩展信息

Request URI: https://{IDaaS_server}/api/bff/v1.2/oauth2/userinfo/extra

  • 接口说明:获取应用账户扩展信息

  • 请求方式:GET

  • 请求参数 ​

参数

类型

是否必选

示例值

描述

access_token

String

eyJhbGc1NiIs…

Access Token

  • 返回参数响应示例


{
  "success": true,
  "code": "200",
  "message": null,
  "requestId": "1667302908374$df5dc0f0-b358-cf8d-877d-20381d841934",
  "data": {
    "extendFields": {
      "major": "计算机",
      "age": 30
    }
  }
}
  • 参数说明


参数

类型

示例值

描述

success

boolean

true

是否成功

code

String

200

状态码

message

String

null

返回消息

requestId

String

B3776BB1-930F-4581-B4C3-18F2D7D136CA

请求ID

data

Object

响应数据

└extendFields

Object

扩展信息(扩展字典)

  • 错误码说明


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.

发生未知错误

2.3.5.5. 、刷新用户令牌

当用户的令牌(access_token)快过期,或过期后,如不想再次让用户重新登录,可使用5.1步骤返回的刷新令牌(refresh_token)再次刷新用户的令牌,成功刷新后,将会返回该用户一个新的令牌和刷新令牌。注意在调用时,每次返回的刷新令牌也会有一个过期时间,其值请在OAuth2应用中设置和查看,过期后不能调用成功。
image.png
使用refresh_token换取用户令牌地址格式为:
https://{IDaaS_server}/oauth/token?grant_type=refresh_token&refresh_token={refresh_token}&client_id={client_id}

注: OAuth支持多种grant_type 这里使用的是refresh_token模式。

  • 接口说明:刷新用户令牌

  • 请求方式:POST

  • 请求参数


参数

类型

是否必选

示例值

描述

grant_type

String

refresh_token

固定值
refresh_token

refresh_token

String

eyJhbGciOi…

刷新token

client_id

String

oauth2 client_id

OAuth2 client_id

  • 返回参数示例:

{
    "access_token": "eyJhbGc1NiIs...",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 7199,
    "scope": "read",
    "jti": "956901cd-5669-411f"
}


参数

类型

示例值

描述

access_token

String

eyJhbGc1NiIs…

Access Token

token_type

String

bearer

Token 类型

refresh_token

String

eyJhbGciOi…

刷新token

expires_in

String

7199

Access Token 过期时间

scope

String

read

申请的权限范围

jti

String

956901cd-5669-411f

当令牌是jwt格式时,该值表示令牌的id



  • 返回异常示例

    • client_id 错误:

{
    "error": "invalid_client",
    "error_description": "Bad client credentials"
}
  • refresh_token 错误:

{
    "error": "invalid_token",
    "error_description": "Cannot convert access token to JSON"
}
  • grant_type 错误:

{
    "error": "unsupported_grant_type",
    "error_description": "Unsupported grant type: refresh_token1"
}


3. 常见QA

3.1. 如何强制用户登录认证?

3.1.1. 方法一【注销session】

在登录接口增加prompt参数, 当prompt=login则强制跳转登录页 , 也就是在下图 Authorize URL后面增加”&prompt=login”则不论用户是否已经登录认证,都会展示登录页,用户必须进行一次认证,才可继续单点登录流程,地址格式如下:

https://{IDaaS_server}/oauth/authorize?response_type=code&scope=read&client_id={client_id}&redirect_uri={your_register_callback_uri}&state={state}&prompt=login

3.1.2. 方法二【注销session或token】

IDP 4.11 及以上的版本才支持。

在应用详情中找到 SLO 地址,可以通过该地址清除 IDaaS session、cookie、token,达到登出 IDaaS 的目的。
该接口支持GET、POST访问,POST 时 redirect_url 和 access_token 可使用表单提交,如果不借助浏览器跳转只使用接口调用,则只能清除 access_token。
redirect_url 为登出后跳转地址为可选项,若有值,则登出后重定向到该地址,若没值,则重定向到IDaaS登录页(在该登录页登录后会进入 IDaaS 主页)。
地址格式如下:

https://{IDaaS_server}/public/sp/slo/{应用ID}?redirect_url={redirect_url}&access_token={access_token}

image.png


3.2. 如何判断用户是否已经在IDaaS平台中登录?

在登录接口增加prompt参数, 当prompt=none则获取是否在IDaaS平台登录 , 也就是在下图 Authorize URL后面增加”&prompt=none”,如果能直接跳转Redirect URI标识已IDaaS登录,未登录则会响应interaction_required错误
地址格式如下:

https://{IDaaS_server}/oauth/authorize?response_type=code&scope=read&client_id={client_id}&redirect_uri={your_register_callback_uri}&state={state}&prompt=none

响应如下则代表未登录
image.png

3.3. 如何保存初始发起页面?

在SP发起一个SSO请求的时候, SP需要能够把对应的URL, 保存在内存中, 并和OAuth 中的State 参数关联起来。 这样, 在IDaaS返回State 后, 可以找到当初的URL, 并跳转到这个URL, 实现DeepLinking。比如使用了JAVA的Spring框架的话, 可以用SavedRequest来完成。