无论是自然资源还是互联网上的资源,需要控制使用权与被使用权,以保护资源的安全、合理的使用和有效的管控。
项目中,我们需要控制的是用户资源,既要保证有效用户的合理使用,又要防范非法用户的攻击。如此,如何区分有效和非法就是我们需要考虑的问题,简单点,通过账号密码来区分,能够通过检测的便是有效用户。
可当项目越来越复杂,用户还想使用第三方应用,那么不能将账号密码交给第三方吧,同时,尽管通过账号密码检测了是有效用户,那么是否就能够访问资源、使用资源呢,这也未必。
种种因素下,OAuth(Open Authorization)作为一个安全、开放且简易的标准,方便我们在项目中协调资源、控制授权,以在用户、第三方应用和用户资源三者间形成一个有效的控制机制,而无需将用户的账号密码提供给第三方应用,且还能控制第三方应用对资源的访问与使用。
An open protocol to allow secure authorizationin a simple and standard method from web, mobile and desktop applications.
认证与授权
对于第三方应用想要访问被保护的资源,应当是使用授权访问,而这前提是要经过认证环节,毕竟认证成功,但不代表就有权限访问资源。OAuth更多的是侧重于如何实现授权来控制第三方应用能够访问资源。认证协议是基于其上的Open ID Connect。尽管OAuth的流程中存在认证部分内容,但不能说OAuth就是认证协议。
- Authentication(认证) 是验证用户(是第三方应用的使用者)的身份的凭据(账号和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
- Authorization(授权) 发生在 Authentication(认证) 之后。其本身意思就很明了,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定的权限才能访问。比如手机浏览器想要访问底层摄像头,需要经用户允许访问才能获得授权,以此才能获取到具体资源。
OAuth历史
OAuth 的历史版本中有 OAuth 1.0 和 OAuth 2.0,其中 OAuth 1.0 在 2010 年发表为 RFC5849,OAuth 2.0 为 RFC6749,并且 OAuth 2.0 不向下兼容 OAuth 1.0,目前 OAuth 2.0 被广泛使用,因此这里不再对 OAuth 1.0 进行更多的描述。
OAuth的几种角色
Resource Owner:资源所有者,拥有资源的人,简单说就是谁在操作浏览器/App/小程序..
Resource Server:资源服务器,资源拥有者拥有的资源
Client:第三方应用,从资源所有者处获取授权,然后从资源服务器获取资源
当不考虑OAuth协议,我们常见便是如上三种,想要第三方应用可以使用资源服务器的资源,又得把自身的账号密码交付给第三方应用,带来不安全。如此再加入一个角色,以平衡控制。Authorization Server:授权服务器,提供给资源所有者完成授权并确定允许给第三方应用可以使用的资源范围与操作。由资源服务器方负责实现。
如此一来,资源所有者将账号密码提供给授权服务器来完成验证,验证有效由授权服务器来负责提供能够代表身份的标识给第三方应用,这样一来降低了账号密码泄露的可能。
OAuth的工作流程
获取授权过程:
(A) 用户(Resource Owner)访问第三方应用(Client),第三方应用(Client)向授权服务器(Authorization Server)发起请求,即第三方应用(Client)向用户(Resource Owner)请求授权。
(B)用户(Resource Owner)输入账号密码,勾选允许的资源范围,提交表单,授权服务器验证后返回授权许可给第三方应用(Client)。
(C) 第三方应用(Client)再带着授权许可请求授权服务器(Authorization Server)换取访问令牌(Access_token)。
(D) 授权服务器(Authorization Server)验证第三方应用(Client)的身份和授权许可,然后给第三方应用(Client)返回访问令牌(Access_token)。
获取资源过程:
(E) 第三方应用(Client)拿访问令牌(Access_token)去请求资源服务器(Resource Server)获取资源。
(F) 资源服务器(Resource Server)返回允许范围内的资源给第三方应用(Client)。
OAuth的几种授权方式
在增加了一个Authorization Server角色后,存在几种常用获取授权许可(资源所有者授权能够访问资源服务器内有效范围资源的凭据,供第三方应用来访问资源服务器)的方式。
- Authorization Code:授权码
- Implicit:隐式授权
- Client Credentials:客户端凭证授权
- Resource Owner Password Credentials:资源所有者密码凭证授权
如上四种模式有一个前提是第三方应用需要在授权服务器中有过登记注册过程。不应该让随便一个第三方应用需要请求资源,就跳转到授权服务器,然后让资源所有者完成授权,然后便可去访问资源。而应该是有效的第三方应用,何为有效,在授权服务器中有过登记,并返回给第三方客户端一对识别码,客户端Id和客户端密钥,由第三方应用私有保存,这个很是常见。
授权码
授权码是目前 OAuth 2.0 中最常用的。通过去授权端获取授权码,利用授权码换取 token,通过使用 token 去资源服务器获取受保护资源。
核心部分在于红色部分,细讲这几部分
1、当前访问的客户端的URL地址重定向到授权服务器的授权地址(2.0~2.1)
https://authorization-server.com/auth
?response_type=code
&client_id=29352915982374239857
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&scope=create+delete
&state=xcoiv98y2kd22vusuye3kch
参数说明:
-
response_type=code
必选,向授权服务器标识客户端使用授权码方式 -
client_id
必选,应用的身份标识Id(应用先得在授权服务器中登记得到一对ClientId和Client Secret)。 -
redirect_uri
可选,授权成功后从授权服务器重定向回client的地址。 -
scope
可选,表示Client申请授权的范围,用一个或多个空格分隔。 -
state
推荐,Client提供的一个字符串,服务器会原样返回给Client,以阻止CSRF攻击。
2、授权页中同意授权,重定向回Client提供的重定向地址(3.0~3.3)
https://example-app.com/redirect
?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
&state=xcoiv98y2kd22vusuye3kch
参数说明:
-
state
为Client提供的字符串,授权服务器原样返回,用于Client防止CSRF攻击。 -
code
授权服务器生成的授权码(临时的)。
3、授权码换访问令牌(4.0~4.2)
重定向回到Client后,Client获取到授权码,向授权服务器发送Post请求,以换取访问令牌。
POST
https://authorization-server.com/auth/token
Content-Type: application/x-www-form-urlencoded
参数:
grant_type=code
&code=Yzk5ZDczMzRlNDEwY
&redirect_uri=https://example-app.com/cb
&client_id=mRkZGFjM
&client_secret=xRsjfkgfgfdfvxsfg
参数说明:
-
grant_type=authorization_code
必选,向授权服务器标识客户端类型为授权码类型。 -
code
必选,上一步中重定向返回的授权码。 -
redirect_uri
必选,和第一步中提供的redirect_uri相同。 -
client_id
必选,客户端Id,第一步中提及的应用标识Id。 -
client_secret
必选,客户端标识Id对应密钥,用于授权服务器对客户端身份验证,并防范授权码和客户端Id暴露被直接用来换取访问令牌。
授权服务器验证客户端身份以及授权码正确及有效后返回访问令牌。
{
"access_token": "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
"scope": "create delete"
}
核心部分授权码流程结束,后续便是拿着访问令牌获取资源。
问题解惑
虽然整个流程算是清晰了,但还是存在一些疑惑,如下所示,也随着更多的了解也有了答案。纯属自问自答了。
1、授权码是一个临时的,用来令Client向Authorization Server换取访问令牌(Access token),那么这一次获取到访问令牌,并获取到了资源后。User再次访问同样的资源,这个授权码换取访问令牌的过程没有了,那么如何从哪里获取访问令牌以去访问资源呢?
答:除了返回Access_token还返回了一个Refresh_token。Access_token有短暂的生命周期,如果过期了就用Refresh_token去刷新或是获取一个新的Access_token。Refresh_token的生命周期更长。
2、当一次访问资源的流程结束后,下一次再访问时,如何可以不要再登录了,也就是这个流程中并没返回标识信息到User,如何判断User有效并可以不用重定向到用户登录页?
答:该部分标识用户身份属于OIDC的部分与OAuth无关,OAuth目的是访问受保护的资源。OIDC中有个ID token,一般为JWT格式,用于返回给前端,然后请求时携带着以标识用户身份。又或者是采用Cookie+Session方式来标识用户。具体点就是从AuthServer重定向到Client时已经带上了身份标识信息。
3、Refresh_token是需要Client端存起来吗,如何标识这个Client端对应是哪个User?
答:标识Client端对应的是哪个User,这部分不是OAuth2.0协议所关注的,这部分更多是OIDC协议的事情。至于Refresh_token的存储,如下方式可以。
- 如果User和Client间是Cookie和Session,那么可以通过将标识写入到Cookie中,同时本地Session中记录UserId和Refresh_token,这是可以做到的,由Cookie和Session来标识用户是谁。
- 如果是使用Jwt,那么或许是存在Cache(Memory、Redis)或是直接存在数据表中,因为已经知道了UserId以及Refresh_token的有效期,这些都可以存储起来完成对应关系。
需要注意
- Access_token和Refresh_token其核心在于,Client端请求资源的访问令牌对外隐藏,只在Client端、Authorization Server和Resource Server间使用。
- 标识这个用户身份以及如何确保用户下次访问仍是这个用户并不是OAuth2.0所关心的,OAuth2.0更多是偏向于对保护资源的有效访问,核心部分聚焦于此。
隐式授权
在授权码的环节上做了一些简化,原有返回授权码的过程变成了直接返回访问令牌。这种情况在纯前端Web应用中(如SPA、浏览器插件)常出现,没有后台,在浏览器中需要负责完成所有过程。
相比于授权码形式,少了Client Server与Authorization Server完成交换Access Token过程,而是直接将返回授权码替换成了返回Access Token(这种方式存在着一些安全问题)。除非没得选,不然不会考虑这种方式,因为提出这种方式是十年前,浏览器功能受限,现如今功能更加强大,更多是采用授权码类型加PKCE来代替隐式授权类型。
https://developer.okta.com/blog/2019/05/01/is-the-oauth-implicit-flow-dead
核心部分在于红色部分,细讲这几部分
1、当前访问的客户端的URL地址重定向到授权服务器的授权地址(2.0)
https://authorization-server.com/auth
?response_type=token
&client_id=29352915982374239857
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&scope=create+delete
&state=xcoiv98y2kd22vusuye3kch
参数说明:
-
response_type=token
必选,向授权服务器标识客户端使用隐式授权方式 -
client_id
必选,应用的身份标识Id(应用先得在授权服务器中登记得到一对ClientId和Client Secret)。 -
redirect_uri
可选,授权成功后从授权服务器重定向回client的地址。 -
scope
可选,表示Client申请授权的范围,用一个或多个空格分隔。 -
state
推荐,Client提供的一个字符串,服务器会原样返回给Client,以阻止CSRF攻击。
2、授权页中同意授权,重定向回Client提供的重定向地址(3.0~3.3)
https://example-app.com/redirect
#access_token=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
&state=xcoiv98y2kd22vusuye3kch
&expires_in=3600
&token_type=Bearer
&scope=create
参数说明:
-
state
为Client提供的字符串,授权服务器原样返回,用于Client防止CSRF攻击。 -
access_token
授权服务器生成的访问令牌(临时的)。 -
token_type
访问令牌默认的schema。 -
expires_in
访问令牌的过期时间(秒数)。 -
scope
Client申请授权的范围。
问题解惑
1、这样返回的Access token生命周期很短同时又不会返回Refresh token,如果多次请求,那么就失效了得重新登录了?
答:隐式授予流程不能用于获取刷新令牌,由于基于浏览器的应用基本上都是短时的连接,仅持续加载它们的浏览器的上下文的会话长度,因此,刷新令牌的用途非常有限。与其他授予类型不同,可以假定资源所有者仍处于浏览器中,并在必要的时候可用于重新授权客户端。授权服务器仍能够应用首次使用信任(TOFU)原则从而使重新认证成为无缝的用户体验。
需要注意
重定向返回时的返回参数形式上和授权码模式有所不一样,隐式授权将token信息放在了url的hash部分(#后面),而不是作为查询字符串(?后面)。这样浏览器在访问重定向的Location指定的url时,就不会把这些数据发送到服务器。而Client可以通过读取Location头信息中获取到access_token信息。
客户端凭证授权
有些场景下,资源所有者不能直接参与,即没有与用户的交互这一过程,像后台任务,定时Job等是直接由Client Server去请求Authorization Server,这种即Machine to Machine。
核心部分在于红色部分,细讲这几部分
1、当前客户端发起Post请求授权服务器(4.1)
POST请求
https://authorization-server.com/auth
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
scope=create+delete
携带请求头,其中Basic对应值xxx为clientid:clientsecret然后经base64编码
Authorization: Basic xxx
客户端提供以下参数请求Authorization Server:
- grant_type:必选。值固定为“client_credentials”。
- scope:可选。表示授权范围。
2、授权服务器认证Client端信息无误后返回访问令牌(4.2)
参数说明:
-
access_token
授权服务器生成的访问令牌(临时的)。 -
token_type
访问令牌默认的schema。 -
expires_in
访问令牌的过期时间(秒数)。 -
scope
Client申请授权的范围。
资源所有者密码凭证授权
这种方式将资源所有者的身份标识信息(账号密码,提供给Client,然后Client再去授权服务器换取访问令牌),这种安全风险大,更多是完全信任Client才会使用。
核心部分在于红色部分,细讲这几部分
1、当前客户端发起Post请求授权服务器(4.1~4.2)
POST请求
https://authorization-server.com/auth
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=yourname&password=123qwe
请求头,其中Basic对应值xxx为clientid:clientsecret然后经base64编码
Authorization: Basic xxx
- grant_type:必选。值固定为“password”。
- username:必选。用户登陆名。
- passward:必选。用户登陆密码。
- scope:可选。表示授权范围。
参考
https://developer.okta.com/
https://www.cnblogs.com/linianhui/p/oauth2-authorization.html#auto-id-4
2022-02-26,望技术有成后能回来看见自己的脚步