最近写的程序涉及到了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返回的信息中可以包含组织职位等信息
- 权限管理