前言:当前公司项目是Spring cloud项目,于是乎,开始学习分布式相关技术
首先先了解一下什么Oauth协议,主要解决了什么问题。
先给大家举两个栗子
栗子一: 小新现在想要使用一个“在线打印服务”来打印一些照片,同时小新的照片都存储在了“云网盘”上,按照传统的方式小新要怎么做呢?
1、将照片从“云网盘”上down下来,在上传到“在线打印服务”,然后开始打印。 2、下载/上传太麻烦了,小新可以直接把“云网盘”的账号和密码告诉“在线打印服务”,由“在线打印服务”下载照片在上传。 对于上面的两种方法,方法一太麻烦但是相对于小新来说是安全的;方法二对于小新是比较方便,但是将账号密码告诉了“在线打印服务”就相当于把所有的“云网盘”资料交给了它,这肯定是不可取的。
栗子二: 假如我有一个网站,你是我网站上的访客,看了文章想留言表示「朕已阅」,留言时发现有这个网站的帐号才能够留言,此时给了你两个选择:一个是在我的网站上注册拥有一个新账户,然后用注册的用户名来留言;一个是使用github帐号登录,使用你的github用户名来留言。前者你觉得过于繁琐,于是惯性地点击了github登录按钮,此时OAuth认证流程就开始了。 需要明确的是,即使用户刚登录过github,我的网站也不可能向github 发一个什么请求便能够拿到访客信息,这显然是不安全的。就算用户允许你获取他在github上的信息,github为了保障用户信息安全,也不会让你随意获取。所以操作之前,我的网站与github之间需要要有一个协商。
所以,为了解决类似上面的问题,Oauth协议诞生了。
OAuth 即 Open standard for Authorization OAuth是一个网络开放协议。为保证用户资源的安全授权提供了简易的标准
oauth的好处: 1.允许用户授权第三方网站或应用,访问用户存储在其它网站上的资源,而不需要将用户名和密码提供给第三方网站或分享他们数据的内容 2.对于用户:免去了繁琐的注册过程,降低了注册成本,提高了用户体验 3.对于消费方:简化自身会员系统的同时又能够带来更多的用户和流量。 4.对于服务提供者:围绕自身进行开发,增加用户粘性
目前oauth和版本是2.0即oauth2.0,而且不向下兼容。本文章主要针对oauth2.0进行讲解。
在介绍协议流程之前先要说明一下oauth2.0定义的几个角色:
resource owner:资源所有者,这里可以理解为用户。 client:客户端,可以理解为一个第三方的应用程序。 resource server:资源服务器,它存储用户或其它资源。 authorization server:授权服务器,它认证resource owner的身份,为 resource owner提供授权审批流程,并最终颁发授权令牌(Access Token)。 useragent:用户代理,这里可以理解为“浏览器”。
这里面有个需要注意的地方,这里只是在逻辑上把authorization server与resource server区分开来;在物理上,authorization server与resource server的功能可以由同一个服务器来提供服务。
(A)用户打开客户端以后,客户端要求用户给予授权。 (B)用户同意给予客户端授权。 (C)客户端使用上一步获得的授权,向认证服务器申请令牌。 (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 (E)客户端使用令牌,向资源服务器申请获取资源。 (F)资源服务器确认令牌无误,同意向客户端开放资源。
上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。 下面就介绍一下oauth2.0获取授权的几种方式。
对于一个应用程序来说,如果它想要使用OAuth,那么首先它要在服务提供商那里注册。 应用程序要提供: 应用程序名称(Application Name) 应用程序网站(Application Website) 回调URL(Redirect URI or Callback URL)
在用户同意授权(或者拒绝)之后,服务提供商会将用户重新导向这个Callback URL,这个Callback URL要来负责处理授权码或者访问令牌。 应用程序注册完成之后,服务提供商会颁发给应用程序一个“客户端认证信息(client credentials)”。
Client Credential包括: Client ID提供给服务提供商,用于识别应用程序用于构建提供给用户请求授权的URL Client Secret提供给服务提供商,用于验证应用程序 只有应用程序和服务提供商两者可知
授权模式
oauth2.0提供了四种授权模式,开发者可以根据自己的业务情况自由选择。 授权码授权模式(Authorization Code Grant) 隐式授权模式(Implicit Grant) 密码授权模式(Resource Owner Password Credentials Grant) 客户端凭证授权模式(Client Credentials Grant)
授权码授权模式(Authorization Code Grant)
(A)用户访问客户端,客户端将用户引导向认证服务器。 (B)用户选择是否给予客户端授权。 (C)如用户给予授权,认证服务器将用户引导向客户端指定的redirection uri,同时加上授权码code。 (D)客户端收到code后,通过后台的服务器向认证服务器发送code和redirection uri。 (E)认证服务器验证code和redirection uri,确认无误后,响应客户端访问令牌(access token)和刷新令牌(refresh token)。 请求示例 (A)步骤:客户端申请认证的URI
代码语言:javascript复制https://www.example.com/v1/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read&state=xxx
参数说明:
response_type:授权类型,必选项,此处的值固定为"code" client_id:客户端的ID,必选项 redirect_uri:重定向URI,必选项 scope:申请的权限范围,可选项 state:任意值,认证服务器会原样返回,用于抵制CSRF(跨站请求伪造)攻击。
(C)步骤:服务器回应客户端的URI
代码语言:javascript复制https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xxx
参数说明:
code:授权码,必选项。授权码有效期通常设为10分钟,一次性使用。该码与客户端ID、重定向URI以及用户,是一一对应关系。 state:原样返回客户端传的该参数的值。
(D)步骤:客户端向认证服务器申请令牌
代码语言:javascript复制https://www.example.com/v1/oauth/token?client_id=CLIENT_ID&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL
参数说明:
client_id:表示客户端ID,必选项。 grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。 code:表示上一步获得的授权码,必选项。 redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
注意:协议里没有提及client_secret参数,建议可以使用此参数进行客户端的二次验证。 (E)步骤:响应(D)步骤的数据
代码语言:javascript复制{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
参数说明:
access_token:访问令牌,必选项。 token_type:令牌类型,该值大小写不敏感,必选项。 expires_in:过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 refresh_token:更新令牌,用来获取下一次的访问令牌,可选项。 scope:权限范围,如果与客户端申请的范围一致,此项可省略。
使用场景 授权码模式是最常见的一种授权模式,在oauth2.0内是最安全和最完善的。 适用于所有有Server端的应用,如Web站点、有Server端的手机客户端。 可以得到较长期限授权。
隐式授权模式(Implicit Grant)
(A)客户端将用户引导向认证服务器。 (B)用户决定是否给于客户端授权。 (C)假设用户给予授权,认证服务器将用户导向客户端指定的”重定向URI",并在URI的Hash部分包含了访问令牌。 (D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。 (E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。 (F)浏览器执行上一步获得的脚本,提取出令牌。 (G)浏览器将令牌发给客户端。 请求示例 (A)步骤:客户端发出请求
代码语言:javascript复制https://www.example.com/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read&state=xxx
参数说明:
response_type:授权类型,此处的值固定为"token",必选项。 client_id:客户端的ID,必选项。 redirect_uri:重定向的URI,必选项。 scope:权限范围,可选项。 state: 任意值,认证服务器会原样返回,用于抵制CSRF(跨站请求伪造)攻击。
(C)步骤:认证服务器响应客户端的请求url
代码语言:javascript复制https://www.example.com/callback#access_token =ACCESS_TOKEN&state=xyz&token_type=example&expires_in=3600&state=xxx
参数说明:
access_token:访问令牌,必选项。 token_type:令牌类型,该值大小写不敏感,必选项。 expires_in:过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 scope:权限范围,如果与客户端申请的范围一致,此项可省略。 state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
使用场景: 适用于所有无Server端配合的应用 如手机/桌面客户端程序、浏览器插件。 基于JavaScript等脚本客户端脚本语言实现的应用。 注意:因为Access token是附着在 redirect_uri 上面被返回的,所以这个 Access token就可能会暴露给资源所有者或者设置内的其它方(对资源所有者来说,可以看到redirect_uri,对其它方来说,可以通过监测浏览器的地址变化来得到 Access token)。
密码模式(Resource Owner Password Credentials Grant)
(A)用户向客户端提供用户名和密码。 (B)客户端将用户名和密码发给认证服务器,向后者请求令牌。 (C)认证服务器确认无误后,向客户端提供访问令牌。 请求示例 (B)步骤:客户端发出https请求
代码语言:javascript复制https://www.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
grant_type:授权类型,此处的值固定为"password",必选项。 username:用户名,必选项。 password:用户的密码,必选项。 scope:权限范围,可选项。
(C)步骤:向客户端响应(B)步骤的数据
代码语言:javascript复制{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
access_token:访问令牌,必选项。 token_type:令牌类型,该值大小写不敏感,必选项。 expires_in:过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 refresh_token:更新令牌,用来获取下一次的访问令牌,可选项。
使用场景: 这种模式适用于用户对应用程序高度信任的情况。比如是用户操作系统的一部分。 认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
客户端凭证模式(Client Credentials Grant)
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。 (B)认证服务器确认无误后,向客户端提供访问令牌。 请求示例 (A)步骤:客户端发送https请求
代码语言:javascript复制https://www.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID
granttype:表示授权类型,此处的值固定为"client_credentials",必选项。 scope:表示权限范围,可选项。
(B)步骤:向客户端响应(A)步骤的数据
代码语言:javascript复制{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"example_parameter":"example_value"
}
access_token:访问令牌,必选项。 token_type:令牌类型,该值大小写不敏感,必选项。 expires_in:过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 example_parameter:其它参数,可选项。
使用场景: 客户端模式应用于应用程序想要以自己的名义与授权服务器以及资源服务器进行互动。 例如使用了第三方的静态文件服务
刷新TOKEN
从上面的四种授权流程可以看出,最终的目的是要获取用户的授权令牌(access_token)。而且授权令牌(access_token)的权限也非常之大, 所以在协议中明确表示要设置授权令牌(access_token)的有效期。那么当授权令牌(access_token)过期要怎么办呢,协议里提出了一个刷新token的流程。 流程介绍
(A)–(D)通过授权流程获取access_token,并调用业务api接口。 (F)当调用业务api接口时响应“Invalid Token Error”时。 (G)调用刷新access_token接口,使用参数refresh_token(如果平台方提供,否则需要用户重新进行授权流程)。 (H)响应最新的access_token及refresh_token。
请求示例 (G)步骤:客户端调用刷新token接口
代码语言:javascript复制https://www.example.com/v1/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
client_id:客户端的ID,必选项。 client_secret:客户端的密钥,必选项。 grant_type:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。 refresh_token:表示早前收到的更新令牌,必选项。
(H)步骤:响应客户端数据
代码语言:javascript复制{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
access_token:访问令牌,必选项。 token_type:令牌类型,该值大小写不敏感,必选项。 expires_in:过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 refresh_token:更新令牌,用来获取下一次的访问令牌,可选项。 scope:权限范围,如果与客户端申请的范围一致,此项可省略。
说明:建议将access_token和refresh_token的过期时间保存下来,每次调用平台方的业务api前先对access_token和refresh_token进行一下时间判断,如果过期则执行刷新access_token或重新授权操作。refersh_token如果过期就只能让用户重新授权。