资源授权?对OAuth2.0的一次重新认识的过程

什么是OAuth?

OAuth一个开放的授权标准允许用户在不提供关键信息(如账号,密码)给第三方应用的前提下,让第三方应用去访问用户在某网站上的资源(如头像,用户昵称等)

OAuth分为OAuth1.0和OAuth2.0两个版本,后来随着OAuth2.0被使用的越来越广泛,OAuth1.0逐渐退出舞台(当然仍有少部分系统在使用1.0授权标准)。下面我们围绕OAuth2进行展开。

OAuth 2.0致力于简化客户端开发人员的工作,同时为Web应用程序,桌面应用程序,移动电话和客厅设备提供特定的授权流程。

我们以博客园登录为例:

新同学小明想在“博客园”发表文章,这时候他进入登录页面,“博客园”登录首页需要小明提供用户名和密码才允许小明登入。

 而由于小明是第一次使用“博客园”,他对博客园可能不是很信任,于是他想通过下方的他信任QQ应用直接登录。

来到这里我们发现,通过授权,“博客园”将获得小明在QQ应用中的资源(昵称,头像,性别),并成功登录“博客园”,而并不需要提供用户名和密码给“博客园”。

上例中“博客园”就是所谓的第三方应用,而QQ就是提供受保护资源的某个应用。

OAuth2四个参与角色

1.资源所有者(Resource Owner):资源的拥有者(上例中:小明)
2.资源服务器(Resource Server):资源所在的服务器(上例中:QQ应用)
3.授权服务器(Authorization Server):用于验证client(第三方应用)的真实性,提供授权码和令牌token。授权服务器可单独部署,也可以和资源服务器一起部署。
4.第三方应用(Client):访问受保护资源的客户端,上例中:博客园

这里,很多同学会对“授权服务器”存在疑惑,上例中博客园登录流程并未体现授权服务器啊?我们接着往下看

OAuth2标准授权流程

结合OAuth2经典流程图,我们一下再来看下小明登录“博客园”的流程。

(A)第三方应用(client)“博客园”向(资源拥有者)小明发起授权请求,即小明点击博客园登录页中的QQ登录。

(B)QQ登录授权页面,小明登录自己的QQ账号,同意授权给“博客园”,并返回授权许可凭证。

(C)第三方应用(client)“博客园”拿着步骤(B)获取的授权许可凭证,向授权服务器发起请求。

(D)授权服务器同意第三方应用(client)“博客园”的请求,并返回一个令牌Token。

(E)第三方应用(client)“博客园”拿着Token请求(资源服务器)QQ应用中的昵称,头像等资源

(F)(资源服务器)QQ应用验证Token通过,并返回资源给第三方应用(client)“博客园”

通过上述流程,我们可以看出,当QQ授权同意后,并不是马上就将QQ应用中的资源返回给第三方客户端的,而是需要客户端拿着授权许可凭证,向授权服务器请求并获取最终的钥匙Token,然后才能获得受保护资源。

显然,授权服务器充当了一个验证client,并颁发token令牌的服务角色。

那么授权许可凭证到底是什么呢?

OAuth2授权许可凭证

1.授权码(Authorization Code)

 该模式目前是功能最完整、流程最严密的授权模式。一般需要client有专门的后端server,根据获取的授权码code在后端server请求令牌token。

上例中博客园登录授权就是用的“授权码模式”,交互流程如下:

1)博客园向小明发起QQ授权请求。

2)QQ授权验证同意后,根据博客园提供的Redirect_url返回,并带上授权码code

3)博客园前端根据返回的url获取授权码code,并在后端server向授权服务器发起请求获取token

4)授权服务验证通过,并以json格式返回token

5)博客园再根据token请求QQ应用中获取受保护资源(头像,昵称等)

   步骤(1),授权请求(Authorization Request):

   client需提供如下主要参数:

   1.client_id:必填,第三方应用唯一标识ID

   2.redirect_uri:必填,授权同意后,重定向地址URL

   3.response_type:必填,授权码模式下固定值为“code”

   4.state:选填,一个状态码,可用于防止跨站请求伪造(CSRF)攻击。客户端发起授权请求时会生成一个状态码与客户端绑定,授权请求成功后会将该state原样返回

   5.scope:选填,标识授权范围

   例如博客园向QQ发起授权请求:

https://graph.qq.com/oauth2.0/show?which=Login&display=pc
&client_id=101880508
&scope=get_user_info
&response_type=code
&redirect_uri=***
&state=*****

    扩展:跨站请求伪造(CSRF)攻击:用户登录了A可信网站,认证信息保存在浏览器cookie中。当用户访问攻击者创建的B网站时,用户认证信息仍有效,攻击者通过在B网站发送一个伪造的请求提交到A网站服务器上,让A网站服务器误以为请求来自于自己的网站。  

 步骤(2),授权请求同意后,返回信息:

  1.code:授权码

  2.state:原样返回,client提交的state状态码

  步骤(3),根据授权码code,后端请求token,client需提供如下参数

1.grant_type:必填。授权码模式,固定值“authorization_code”。

2.code : 必填。授权同意后返回的授权码。

3.redirect_uri:必填。授权同意后,重定向地址URL

4.client_id:必填。第三方应用唯一标识ID。

5.client_secret:必填。第三方应用授权申请的秘钥。

 例如:

POST /oauth/token HTTP/1.1
Host: authorization-server.com
 
grant_type=authorization_code
&code=xxxxxxxxxxx
&redirect_uri=https://example-app.com/redirect
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

步骤(4),授权认证通过,返回主要参数。

1.access_token:访问令牌。

2.refresh_token:刷新令牌。

3.expires_in:令牌过期时间。

4.token_type:令牌类型。

其中刷新令牌refresh_token的作用是,当访问令牌token失效时,无需重新发起授权获取新的token,根据刷新令牌直接请求授权服务,就可以获取新的令牌。

2.隐式许可(Implicit)

 授权码模式的简化应用,跳过了获取授权码code的过程,直接获取token。一般用于没有后端的Client。

 如果博客园使用该模式,其工作流程:

1)博客园向小明发起QQ授权请求。

2)QQ授权验证同意后,根据博客园提供的Redirect_url返回,并带上令牌token

3)博客园再根据token请求QQ应用中获取受保护资源(头像,昵称等)

  步骤(1),授权请求参数和授权码模式参数一样,唯一不同的参数是response_type,Implicit模式下固定值为“token”。

  步骤(2),授权请求同意后,返回信息:

1.access_token:访问令牌。

2.token_type:令牌类型。

3.expires_in:令牌过期时间。

  注:隐式授权模式颁发的令牌,不提供refresh刷新令牌

URL格式:

格式://a.com/callback#token=ACCESS_TOKEN

注意,令牌的位置是 URL 锚点“#”后面,而不是查询字符串“?”后面,这是因为 OAuth2允许跳转网址是 HTTP 协议,因此存在”中间人攻击”的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

3.用户密码模式(Resource Owner Password Credentials)

 客户端提供用户名和密码,获取token。前面我们说OAuth2就是为了避免直接提供用户名和密码给第三方应用程序而诞生,那么这里又是怎么回事呢?

 其实,该授权模式的初衷是为服务自己的应用启用密码登录,用户使用其用户名和密码登录该服务的网站或本机应用程序,但是绝对不允许第三方应用程序询问用户密码。

 授权请求,提供参数:

1.grant_type:必填。该模式下固定值为“password”。

2.username:必填。用户登陆名。

3.passward:必填。用户登陆密码。

4.scope:可填。表示授权范围。

5.客户端认证参数:通常如果授权服务管理系统给客户端颁发了身份秘钥信息(client_id,client_secret),那么客户端发起授权请求时需要携带参数client_id和client_secret。或在HTTP Basic auth标头中接受客户端client_secret和密码client_secret

   其中,客户端认证参数:client_id和client_secret主要是用于授权服务验证客户端的身份。如果客户端都没在授权服务管理系统备案(不需要验证客户端身份),那么授权请求就不需要这两个参数。备案又是什么呢?大家请留意文章末尾。

   以postman请求token为例:

   第一种方式:将client_id和client_secret作为请求体参数

 第二种方式:在HTTP Basic auth请求头中单独验证client_secret和client_secret

 

 

 

授权请求同意后,返回主要参数:

1.access_token:访问令牌。

2.token_type:令牌类型。

3.expires_in:令牌过期时间。

4.scope:授权范围

4.客户端模式(Client Credentials)

 当第三方应用程序请求访问令牌以访问其自己的资源(而非代表其他用户去访问资源)时,使用该模式。此时第三方应用程序将自己当成资源所有者,直接请求授权服务器获取令牌token。

 授权请求,提供参数:

1.grant_type:必填。该模式下固定值为“client_credentials”。

2.scope:可填。表示授权范围。

3.客户端认证:必填,包含参数client_id和client_secret;或在HTTP Basic auth标头中接受客户端
client_secret和密码client_secret

以postman请求token为例:

第一种方式:将client_id和client_secret作为请求体参数

 第二种方式:在HTTP Basic auth请求头中单独验证client_secret和client_secret

 

 

 

两种方式效果一致,都是通过client_id和client_secret,让授权服务器验证客户端的身份。

授权请求同意后,返回参数:

1.access_token:访问令牌。

2.token_type:令牌类型。

3.expires_in:令牌过期时间。

4.scope:授权范围

扩展:第三方应用发起授权请求时,client_id和client_secret是哪来的呢?

授权服务提供第三方应用的管理:

第三方应用申请令牌之前,都必须先到授权服务系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。

5.刷新令牌refresh  token

前面我们说,当授权请求同意后,通常授权服务器会返回一个刷新令牌refresh  token给我们(该返回参数非必选的,由授权服务定制,通常推荐返回该参数)。

当访问令牌access token 过期后,如果重新发起一遍请求令牌的过程显然有点麻烦,这时候通过refresh token发起一次请求可以直接获取新的访问令牌access token。

请求参数:

1.grant_type:必填。固定值为“refresh_token”。

2.refresh_token:必填。

3.scope:可填。表示授权范围。

4.客户端认证:通常如果授权服务管理系统给客户端颁发了身份秘钥信息(client_id,client_secret),那么客户端发起授权请求时需要携带参数client_id和client_secret。或在HTTP Basic auth标头中接受客户端client_secret和密码client_secret。
如果客户端不需要身份认证,则无需携带任何身份认证的信息

例如:

POST /oauth/token HTTP/1.1
Host: authorization-server.com
 
grant_type=refresh_token
&refresh_token=xxxxxxxxxxx
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

 

本文,我们结合博客园授权QQ登录的案例,描述了OAuth2的基本概念,OAuth2的授权流程以及各种授权模式的使用。多动手,多动手,多动手才能加深自己的理解。

附:推荐几篇值得学习的OAuth2文章

1.OAuth2.0协议标准

2.大神阮一峰的文章:OAuth2.0的四种方式