资源授权?对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格式:
格式:https://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文章