OAuth 2.0 与 OIDC

最近写的程序涉及到了OAuth2.0 和 OIDC,遂仔细理解一下这两个协议

OIDC是OAuth的拓展,所以先从OAuth开始,参考文档

OAuth 2.0

引入

Open Authorization,开放式授权,要解决的核心问题是“在不获取另一应用的密码的情况下,访问到另一应用里受保护的资源”,比如很多app/网站都支持微信登录,然而他们并不需要知道你的微信密码

那么如何实现呢?直接共享密码相当于完全暴露隐私,显然是不可取的,部分应用使用api key,这样一个key对应一个服务,虽然实现了受限访问,但key和密码本质上也没啥区别

于是OAuth应运而生,其核心是使用访问令牌(access token)——一个包含权限范围、持续时长与其他访问属性的字符串。认证服务器在得到用户的批准后将token颁发给第三方应用,这个第三方用这个token就可以访问到受保护的资源

四个角色

为了说明更加清晰,OAuth定义了四个角色:

  • resource owner,资源拥有者,一般来说就是用户
  • resource server 资源服务器,资源实际存放的地方
  • client 客户端,也就是要用资源的第三方应用
  • authorization server 进行身份认证和颁发token的服务器

流程

+--------+                               +---------------+
|        |--(A)- Authorization Request ->|   Resource    |
|        |                               |     Owner     |
|        |<-(B)-- Authorization Grant ---|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C)-- Authorization Grant -->| Authorization |
| Client |                               |     Server    |
|        |<-(D)----- Access Token -------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E)----- Access Token ------>|    Resource   |
|        |                               |     Server    |
|        |<-(F)--- Protected Resource ---|               |
+--------+                               +---------------+

简单说就是,客户端向你请求进行认证,你完成认证,客户端再去授权服务器,认证成功得到token,再拿着token去资源服务器拿东西

展开的话,对于向用户发起请求,更建议的(称为标准的)方式是通过授权服务器作为中介来间接发出,也就是重定向到认证服务器去认证,通过url设置返回地址再回来。直白的说,直接方式是问你用户名密码权限,间接方式是把你送到认证服务器输入密码。所以直接方式还要OAuth干什么?

然后认证成功,客户端得到授权凭证(Authorization Grant),这有四种形式:

  • Authorization Code,其中最常用最安全的,重定向到授权服务器完成身份验证,完成后获得code
  • implicit, 前者的简化,认证成功后直接授予token省略中介材料,有安全风险,已经被 带有 PKCE(Proof Key for Code Exchange)的授权码模式所取代
  • resource owner password credentials 直接授予密码,仅限于客户端和资源服务器是同一家时可用
  • client credentials 一种机器对机器(M2M)的场景,无用户参与,即直接从C环节开始,客户端已有授权服务器信任的id和秘钥,服务器在验证之后直接颁发token

客户端向认证服务器呈递凭证,验证合法后颁发token

客户端向资源服务器呈递token,同样验证有效后回应请求

Access Token 与 Refresh Token

Access Token 一个字符串,包含权限等信息,一般来说对客户端不透明(看不懂)

在获得Access token的同时,一般也会同时获得Refresh Token。前者的过期时间很短,如果每五分钟验证一次身份太过影响使用体验,所以又产生了过期慢得多的Refresh Token

客户端可以在access token验证无效(过期)后用Refresh Token找授权服务器换取新的

客户端

作为流程的第一环,只有受信任的客户端才能开始整个流程,所以在授权开始前,客户端需要去授权服务器注册,如何注册不在本协议范畴,但是重点是获得需要的属性:

  • id && secret : 客户端的标识符,密码不可外泄
  • type: public or Confidential,比如web应用的后端在独立的服务器上,数据可以保证不外泄,所以是机密的;而手机app的后端数据就在本地,就是公开的,需要考虑客户端可能伪造
  • redirect_url : 授权码得到后只能被重定向到唯一合法地址,保证授权码不会外泄

三个Endpoint

OAuth 2.0 的授权过程主要涉及三个网络地址(Endpoint)

Authorization Endpoint (授权端点)

授权服务器的地址,客户端将资源拥有者重定向到这里完成第一步的认证

Token Endpoint (令牌端点)

授权服务器的另一个地址,客户端从这里用授权码换取token

Redirection Endpoint (重定向端点)

客户端的地址,授权服务器会将授权码通过重定向发送到这里,也就是刚才客户端注册时的redirect_url

授权码模式详解

Authorization Request

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

指明response_type为授权码模式;客户端id;推荐带有state字段,一个随机字符串,用来防范跨站请求伪造(CSRF)攻击;重定向uri;权限scope(optional)

Authorization Response

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

在验证通过后,回应302重定向到刚才的redirect_uri,附带code,和state(如果有)

Error Response

对于错误处理,有两种情况。如果错误在redirect_uri和client_id,那么就无法证明客户端的身份,是最危险的情况所以不跳转展示错误信息

如果是其余错误,则携带错误信息返回,包括:

  • invalid_request: 缺少参数、格式错误、重复参数、未知参数等
  • unauthorized_client: 客户端未被授权(如public类型的不能使用授权码模式)
  • access_denied: 资源拥有者或授权服务器拒绝了请求(用户没有完成认证)
  • unsupported_response_type: 授权服务器不支持
  • invalid_scope: scope非法、未知或格式错误
  • server_error: 授权服务器错误
  • temporarily_unavailable: 授权服务器错误
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

error_description和error_uri可选

Access Token Request

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

grant_type必须为authorization_code;授权码code;redirect_uri,必须和之前的一致;client id

Access Token Response

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}

包含两个token,过期时间(秒),token类型,scope

同样,错误处理:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"error":"invalid_request"
}

Refreshing an Access Token

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

grant_type必须为refresh_token,scope可选

Accessing Protected Resources

出示令牌的方式取决于刚才的token_type,分为两类,Bearer和MAC

前者是绝对的主流方式,Authorization: Bearer [Access Token]即可,MAC需要客户端用token进行签名,极少使用

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",
                nonce="274312:dj83hs9s",
                mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

OIDC

OAuth 2.0的流程基本清晰了,接下来是OIDC(OpenID Connect),官网文档 和 logto提供的资料

OIDC 是基于 OAuth 2.0 协议之上的简单身份层(Identity Layer)

OAuth 2.0 负责 授权 (Authorization):即“允许这个应用代替我去拿数据”,OIDC 负责认证 (Authentication):即“证明这个用户是谁”

access token像一把钥匙,拿到就可以获取对应的信息,而OIDC则多带上了身份证,意味着可以根据身份获取不同的“资源”

流程

OIDC引入了三个角色:

  • 终端用户 (End User): 最终用户,对应 OAuth 2.0 的资源所有者
  • 依赖方 (RP): 依赖方,对应 OAuth 2.0 的客户端
  • OpenID 提供者 (OP): 身份认证服务提供者,对应 OAuth 2.0 的授权服务器和资源服务器

在OAuth 2.0的基础上,OIDC的流程只有很微小的变化:

  • 在第一步重定向发起授权请求时,必须包含 scope=openid
  • 在获取access token的同时还会得到一个带有个人身份信息的ID Token

ID token

一个 JWT (JSON Web Token) 格式的字符串,包含以下必要信息:

  • sub (Subject)最重要的,用户的唯一 ID
  • iss (Issuer)签发者,即授权服务器
  • aud (Audience) 受众,客户端(依赖方)
  • exp (Expiration Time) 过期时间
  • iat (Issued At) 签发时间

ID token是给客户端RP看的,可以将access token和用户身份绑定于是避免了“混淆代理人攻击” (Confused Deputy Problem)

单纯的access token不含有用户信息,所以即使只是想知道用户的id也许要再进行一次查询

access token还有过期,而只要id token没过期就不用重新进行身份验证,

userinfo-endpoint

用户信息端点,OP提供的标准化 HTTP API 用于获取经过身份验证的用户详细信息,通过使用访问令牌向该端点发送 GET 或 POST 请求,可以接收到 JSON 格式的用户信息。

ID token里能承载的信息有限,所以定义了这样一个接口获取详细全部信息,按协议在OP_url/userinfo这个路径下,按照协议规范,客户端应通过访问 Discovery Endpoint(路径为 OP_url/.well-known/openid-configuration)来获取服务元数据。在该元数据 JSON 中,userinfo_endpoint即是准确url

Discovery Endpoint

详细文档

元数据文档里的重要数据字段:

  • issuer: The unique identifier of the OpenID Provider. It should be used for validating the tokens.
  • authorization_endpoint: The URL to initiate the Authentication request .
  • token_endpoint: The URL to send the Token request .
  • jwks_uri: The URL to retrieve the JSON Web Key Set (JWKS) for Signing key validation.
  • response_types_supported: The supported response types for the Authentication request .
  • scopes_supported: The supported scopes for the Authentication request .
  • claims_supported: The supported claims for the ID token .
  • token_endpoint_auth_methods_supported: The supported client authentication methods for the Token request .

其它

OIDC是现代身份管理系统的底层技术,它有更多的使用场景:

  • 单点登录 (SSO) OP 统一管理用户的登录状态(Session)。一旦在一个应用登录,其他应用(RP)就可以直接通过 OIDC 拿到 ID Token,无需再次输入密码.用户只需登录一次,即可访问所有关联应用,提高效率。
  • 组织结构管理 OP返回的信息中可以包含组织职位等信息
  • 权限管理