Introduction
OAuth2是一个授权框架, 可以使一个应用程序获取其他HTTP服务, 比如Facebook, 的用户账号的部分权限。 当一个第三方应用程序想要访问用户账号时, OAuth2会把验证的过程委托给含有用户账号信息的应用程序。OAuth2提供了Web, Desktop applications, mobile devices的授权流。
OAuth Roles
OAuth定义了四种角色:
- Resource Owner
- Client
- Resource Server
- Authorization Server
Resource Owner(资源拥有者):User
资源拥有者, 即授权应用程序访问他们账号的用户。 应用程序对用户账号的权限为授权后的token中scope字段指定的范围。
Resource / Authorization Server(资源提供者/授权服务器): API
资源提供者提供被保护的用户账号的信息, 授权服务器验证用户(资源拥有者)的身份信息, 然后签发access token给应用程序。
Client(客户端): Application
Client即想要访问用户账号的应用程序。 在应用程序访问用户账号之前, 必须获得用户的授权。
Abstract Protocol Flow
- 应用程序请求用户授权访问被保护的用户资源。
- 如果用户授权应用程序, 则应用程序会获得一个授权。
- 应用程序向授权服务器(authorization server)表明自己的身份以及用户的授权并请求access token。
- 如果应用程序的身份验证通过并且用户的授权有效, 则授权服务器(authorization server)会签发一个access token给应用程序, 授权完成。
- 应用程序向资源服务器(resource server)发起请求并展示access token。
- 如果access token有效, resource server会提供被保护的资源给应用程序。
在使用OAuth之前, 必须向Authorization Server注册应用程序(application), 注册时需要提供Application name, Application Website, Redirect URI or Callback URL.
应用程序注册完成后, Authorization Server会生成客户端账号信息(client identifier和client secret)。 Client ID是一个公共的字符串被service用来唯一区别应用程序, 以及被用来构建authorization URL; Client secret被用来当应用程序请求访问用户账号时验证应用程序的身份, client secret不能被公开。
Authorization Grant
OAuth2定义了四种授权类型:
- Authorization Code: 应用于server-side应用程序
- Implicit: 应用于手机App或Web应用程序
- Resource Owner Password Credentials: 应用于可信应用程序
- Client Credentials: 应用于应用程序API访问
Authorization Code
授权类型authorization code是使用最广泛的因为它是面向服务端应用, 并且源代码不会公开, 因此不会暴露client secret。 它是基于重定向的, 表明应用程序必须可以和用户代理交互并接受通过用户代理展示的授权码。
授权过程如下:
- 应用程序发起授权请求
授权请求链接类似:
https://cloud.digitalocean.com/v1/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
- 用户授权应用程序
- Authorization Server生成授权码
用户点击"Authorize Aplication" 授权应用程序后, Authorization server会重定向用户代理到应用程序注册时指定的重定向url, 重定向链接中包含授权码:
https://dropletbook.com/callback?code=AUTHORIZATION_CODE
- 应用程序使用授权码请求Access Token
应用程序通过API访问请求access token, 请求携带授权码(authorization code)以及client secret, API类似如下:
https://cloud.digitalocean.com/v1/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL
- Authorization Server生成Access Token
如果验证通过, API会返回access token给应用程序, 结果类似如下:
{"access_token":"ACCESS_TOKEN","token_type":"bearer","expires_in":2592000,"refresh_token":"REFRESH_TOKEN","scope":"read","uid":100101,"info":{"name":"Mark E. Mark","email":"mark@thefunkybunch.com"}}
接下来应用程序就已经被授权了, 应用程序访问被保护用户账号时必须携带access token, 所具有的的权限限定于scope指定的范围。
如果同时返回的还有refresh token, 则当access token过期时可以请求生成新的access token。
Implicit
适用于手机应用或web应用(运行于浏览器中的应用), 这个场景下client secret的安全性得不到保障。这个也是基于重定向的。 这个过程不会验证应用程序的身份, 而是依赖重定向URL验证应用程序的身份。
这个授权方式不支持refresh token。
授权过程:
- 应用程序发出授权请求, 这个授权请求链接类似授权码的授权链接, 除了这个请求的是token而不是code
https://cloud.digitalocean.com/v1/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
- 用户授权应用程序
用户点击同意授权应用程序, 用户必须首先登录被保护的用户账号。 - 用户代理通过重定向URI接收access token
用户点击同意授权后, Authorization server会重定向用户代理到应用程序制定的重定向URI并且包含access token
https://dropletbook.com/callback#token=ACCESS_TOKEN
- 用户代理跟随重定向URI但是保留access token, 之后应用程序从URL重取出access token。
Resource Owner Password Credentials
用户直接提供被保护的用户账号的账号密码给应用程序, 应用程序使用这个账号信息去获取access token。这种授权方式应当仅在其他方式不能使用的时候使用。 并且应该只在可信的应用程序中使用, 比如Authorization server中的程序。
授权链接通常如下:
https://oauth.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
Client Credentials
这种授权方式使得应用程序可以访问它自己的服务账号, 在应用程序需要更新注册信息时有用, 比如描述信息或重定向URI,或者其他信息。
应用程序向authorization server请求access token并携带client id和client secret。
连接通常如下:
https://oauth.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
Access token使用案例:
curl -X POST -H "Authorization: Bearer ACCESS_TOKEN""https://api.digitalocean.com/v2/$OBJECT"
Refresh token使用案例:
https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2
OAuth Scopes
就是授权页面上app请求的权限,这是开发者写死的权限。
示例access_token:
{
"access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type":"bearer", // token类型
"expires_in":3600, // 如果token有过期时间,则应该在过期时间内使用token
"refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk", // 获取新的access_token所用,但是implicit grant没有refresh token
"scope":"create"
}
https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
OpenID Connect
OpenID Connect1.0是基于OAuth2.0协议的简单身份层,允许客户端确定已被Authorization Server验证过的终端用户的身份,以及可以以Restful方式获取终端用户的基本资料(basic profile information).
OpenID Connect允许所有类型的客户端,包括Web, mobile, JavaScript客户端。
OpenID Connect和OpenID2.0做了很多相似的工作,但是OpenID Connect是以一种API友好的方式。
AUth1.0a和OpenID2.0集成需要扩展,而OAuth2.0的功能是集成在OpenID Connect协议中的。
一个完整的Open ID Connect的过程如下:
- 查找OIDC元信息(通常地址为site/.well-known/openid-configuration)
- 执行OAuth流获取id token以及access token
- 访问JWKS终点获取JWT签名密钥并且可选择地注册客户端应用
- 验证JWT ID token(即上文的id token)
- 通过access token访问UserInfo终点获取用户信息
Identity Token
ID token类似于身份证, 是一个标准的JWT格式的由OpenID Provider(OP)提供的字符串。
ID token中包含的内容:
- 用户的身份,subject(sub)
- 签发的授权机构(iss)
- 所属的客户端, audience或者client(aud)
- 可选包含一个随机数(nonce)
- 可选包含签发时间(auth_time),以及强度(acr)
- 签发时间(iat)以及过期时间(exp)
- 可选包含额外的用户信息, 如名称, 邮箱地址等
ID token由header, body, signature三部分组成, 以点(.)分隔,每部分都经base 64转码过。
OpenID Connect授权流:
- Authorization code flow: 最普遍使用的情况, 通常使用在传统web应用, 手机app上。首先重定向用户到OP(Open Identity Proider)进行用户验证并授权, 然后后台发送请求获取id token。 这个方法提供了最好的安全性, 因为token不会传送到浏览器中。
- Implicit flow: 为基于浏览器的, 无后台的app应用。 ID Token直接包含在OP 返回的重定向响应中。
- Hybrid flow: 很少使用, 允许前台, 后台各自请求token, 是以上两种的组合。
OpenID Authorisation code flow详解
分为两步: 1, 验证用户, 请求用户同意, 获取授权码, 2, 可选地验证客户端, 使用授权码获取token
一、
重定向用户到OAuth2.0的授权端点(authorisation endpoint)验证用户。 OpenID验证请求实际上是OAuth2.0的获取用户信息的授权请求, 通过在scope中包含openid字段。
HTTP/1.1 302 Found
Location: https://openid.c2id.com/login?
response_type=code
&scope=openid
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
在OP端检查用户是否已登录, 未登录则提示登录
然后OP会调用客户端应用的redirect_uri并返回authorisation code或者error code
Authorisation code是一个中间码, 客户端应用需要提交这个code到OP获取ID token, 这个可以直接通过后台请求完成, 以避免暴露私密信息和token。
用code交换id token发生在OP的token端点。
Client ID和client secret通过Authorization头部传递, OpenID Connect也支持通过JWT验证。
Token请求通常如下:
POST /token HTTP/1.1
Host: openid.c2id.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fc
如果请求成功, 结果通常是包括ID token, access token和可选的refresh token的json对象。
对于一个基础的OpenID验证请求, 通常只有ID token是需要的, access token可以被忽略, 不过access token也可以用来获取用户的信息, 通过UserInfo端点。
ID token的用途:
- 无状态的session, 可以放进浏览器的cookie中实现轻量级的无状态的session, 避免了在服务器端存储session的必要。
- 传递给第三方: 当其他应用活动后台服务需要知道用户的身份信息时, 可以把ID Token传递给它们。
- Token exchange: 可以使用ID token交换access token. Token exchange使用在分布式应用中。
OpenID well known endpoint
OIDC providers可以支持’well known’端点, 这是一个包含可以让客户端自行配置的json对象, 有些内容不会变化, 有些会定期变化。
https://xxxx.com/.well-known/openid-configuration
一般情况下, 可以使用OpenId Connect替代OAuth2.
https://connect2id.com/learn/openid-connect#example-auth-code-flow-step-1
https://www.onelogin.com/blog/openid-connect-explained-in-plain-english
https://connect2id.com/learn/openid-connect#example-auth-code-flow-step-1
扩展知识:
OpenID Connect Authorization Code Flow
- 客户端准备一个验证请求,包括一些用户指定的参数
- 客户端发送请求到Authorization Server
- Authorization Server验证用户账号,并获取用户的授权后返回Authorization Code
- 客户端利用Authorization code向token端点发请求
- 客户端收到包括ID Token和Access Token的响应
- 客户单验证ID Token,并且获取终端用户的身份标识(Subject Identifier)
验证ID Token和Access Token
ID Token验证
- 通过客户端注册时的key和algorithm解密ID Token
- 验证iss字段(issuer claim)
- 验证aud字段,aud字段必须包含client_id
- 如果有多个audience, 响应必须包含azp
- 如果有azp字段,值必须是client_id
- 其他包括验证alg, iat, nonce等字段
Access Token验证
在使用Authorization Code Flow时,如果ID Token包含at_hash
字段,则客户端必须用它取校验Access Token,验证过程和Implicit Flow使用的一样,但是使用的是从Token Endpoint返回的ID Token和Access Token。
- 使用ID Token中指定的算法对access_token进行hash
- 取hash的左半部分并且base64url encode
- at_hash的值必须和第二步算出的值相等
Implicit Authorization Flow
- 客户端准备验证请求,包含指定的参数
- 客户端发送请求到Authorization Server
- Authorization验证用户账号,并且获取用户授权,然后重定向用户到一个指定地址,url里包含ID Token,如果请求参数里指定了需要Access Token,则url里还有Access Token。
- 客户端验证ID Token并获取用户身份标识(Subject Identifier)
验证ID Token和Access Token
验证过程和Authorization Code Flow相同
Hybrid Authentication Flow
- 客户端准备验证请求,并带有指定的参数
- 客户端发送请求到Authorization Server
- Authorization验证用户账号并取得用户授权,然后返回给客户端Authorization Code,根据指定的Response Type,也可以有一个或多个其他的参数
- 客户端利用Authorization Code向Token Endpoint发送请求,Token Endpoint返回ID Token和Access Token.
- 客户端验证ID Token,并取得用户标识
当使用Hybrid Flow验证流程时,Authorization Endpoint的使用和Authorization Code Flow相同,除了一个参数例外:
response_type
: Oauth2.0 response_type决定了将使用的验证流程。当使用Hybrid Flow时,response_type值可能为code id_token
, code token
或者code id_token token
.
Hybrid Flow Authorization Endpoint返回的值包括以下这些:
access_token
: OAuth2.0 Access Token,当response_type是code token
, code id_token token
时
id_token
: ID Token,当response_type是code id_token
或者code id_token token
时
code
: Authorization Code, Hybrid Flow总是返回这个。
示例:
GET /authorize?
response_type=code%20id_token
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&scope=openid%20profile%20email
&nonce=n-0S6_WzA2Mj
&state=af0ifjsldkj HTTP/1.1
Host: server.example.com
HTTP/1.1 302 Found
Location: https://client.example.org/cb#
code=SplxlOBeZQQYbYS6WxSbIA
&id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso
&state=af0ifjsldkj
想必看到这里大家也明白了Hybrid混合的意思。即可以返回多个token和code, code还可以用来向token Endpoint请求access_token。
Id_token和access_token的验证方式和上文一样。
code
校验流程:
- 使用ID Token头指定的算法hash code的值
- 取左边一半的hash值,然后base64url encode
- 比较c_hash字段的值必须和上一步算出的结果相同
https://openid.net/specs/openid-connect-core-1_0.htm