OAuth 的全方位渗透及node实现

背景

我们先从一个非常典型例子出发,假如你在某网盘上传了你的很多图片,网盘提供了图片存储,另一个某打印店提供了在线打印图片。

由于存储和答应是由两家不同的服务商提供的,两家各自都提供了用户的注册功能,所以如果你要想在打印店上去打印你放在网盘的照片时,此时你会想到2种方案:

  • 假设你的账户密码都不一样,你可以先将待打印的图片从网盘上下载下来,然后在上传到打印店的网站上,之后进行打印。这种模式是最原始,也是效率最低下。
  • 为了省事,你可以将你网盘的账户密码交给打印店老板,告诉他你要打印的图片,让他给你操作,省事,但是风险太大,帐号密码都泄漏了,就不怕别人去篡改个人信息或者查看你的隐私?

之前很多公司包括GoogleYahooMicrosoft都尝试解决这个问题,这也促使OAuth的诞生。

什么是 OAuth ?

OAuthOpen Authorization)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容,目前在全世界范围内得到广泛应用。

OAuth 解决思路

OAuth客户端(Client)打印店资源服务器(Resource Server网盘 之间,设置了一个授权层(Authorization Layer),通过 授权服务器(Authorization Server 提供用户授权。

客户端 要想访问 资源服务器 必须要经过 授权层用户资源拥有者(Resource Owner) 可以在登录的时候,指定授权层令牌的权限范围和有效期,当用户同意授权后才向客户端开放用户储存的资料。

通俗来说:当打印店要访问用户的你网盘的图片时,通过OAuth机制,打印店要向网盘的授权服务器请求授权,网盘服务商将引导你在网盘的网站上登录,并询问你是否将访问图片的服务授权给打印店。当你点击同意后,打印店就可以访问你网盘上的图片服务。整个过程打印店没有触及到你网盘的帐号信息,安全便捷。

OAuth 授权流程

Abstract Protocol Flow

 +--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

大体流程如下:

  • Authrization Request:用户打开客户端,客户端向用户请求对资源服务器的authorization grant,要求用户给予授权。
  • Authorization Grant(Get):用户同意授权请求,客户端将收到一个authorization grant授权许可。
  • Authorization Grant(Post):客户端向授权服务器发送它自己的客户端身份标识和上一步中的 authorization grant授权许可,请求访问令牌。
  • Access Token(Get):认证服务器对客户端身份进行认证,如果认证通过并且 authorization grant 也被验证通过,授权服务器将为客户端派发 access token 访问令牌,授权阶段至此全部结束。
  • Access Token(Post && Validate):客户端向资源服务器发送access token用于验证并请求资源信息。
  • Protected Resource(Get):资源服务器进行请求验证,如果 access token验证通过,资源服务器将向客户端返回资源信息。

授权模式

根据应用请求授权的方式和授权方服务支持的 Grant Type的不同,OAuth 2 定义了四种 Grant Type(授权模式),每一种都有适用的应用场景:

  • 授权码模式(Authorization Code):最常使用的一种授权许可类型,结合普通服务器端应用使用。
  • 隐式授权模式(Implicit):跳过授权码这个步骤,适用于移动应用或 Web App
  • 密码模式(Resource Owner Password Credentials):客户端提供帐号密码,向服务商索要授权,适用于受信任客户端应用
  • 客户端模式(Client Credentials):客户端直接向服务商索取授权,适用于客户端调用主服务API型应用

授权码模式(Authorization Code)

是目前互联网上最常使用的一种授权模式,比如QQ,微博,Facebook和豆瓣等等,它要求第三方应用先申请一个授权码(Authorization Code),然后再用该码获取令牌,请求数据,流程如下:

 +----------+
 | Resource |
 |   Owner  |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+                                          +---------------+
 |         -+----(A)-- User Authorization Request ---->|               |
 |  User-   |                                          | Authorization |
 |  Agent  -+----(B)-- User authenticates ------------>|     Server    |
 |          |                                          |               |
 |         -+----(C)-- Authorization Code Grant ------<|               |
 +-|----|---+                                          +---------------+
   |    |                                                 ^      v
  (A)  (C)                                                |      |
   |    |                                                 |      |
   ^    v                                                 |      |
 +---------+                                              |      |
 |         |>---(D)-- Access Token Request ---------------'      |
 |  Client |                                                     |
 |         |                                                     |
 |         |< ---(E)-- Access Token Grant -----------------------'
 +---------+       (w/ Optional Refresh Token)

User Authorization Request :用户访问客户端,客户端构造了一个用于请求authorization codeURL并引导用户跳转访问,链接格式大概如下:

https://open.server-name.com/oauth2/authorize
    ?response_type=code
    &client_id=AppID
    &redirect_uri=REDIRECT_URI
    &scope=SCOPE
    &state=STATE
  • response_type:授权模式,必选,此时值固定为code
  • client_id:客户端身份标识,一般是注册时候的分配的 AppID
  • redirect_uri:授权成功后重定向地址,必选,注意需要将uri进行URLEncode
  • scope:授权范围,可选
  • state:客户端的状态值,必选,一般是随机字符串,成功授权后回调时会原样带回,为了防止CSRF攻击

User authenticates:用户决定是否给客户端授权,授权服务会提示用户授权或拒绝应用程序访问其帐户信息

Authorization Code Grant:用户确认授权,授权服务器将重定向之前客户端提供的redirect_uri地址,并附带codestate参数,客户端便能取到authorization code的值,链接格式大概如下:

https://client-name.com/redirect_url
 ?code=520DD95263C1CFEA087
 &state=STATE
  • code:授权服务器生成的authorization code,即授权码,code 有效期较短,一般维持在 5 - 10分钟,授权服务器可自行配置
  • state:即客户端之前携带的 state 值,可进行检查对比与最初设置的状态值相匹配,防止CSRF攻击

Access Token Request:客户端获取到授权码 code 后,可向服务器请求(post)获取 access_token ,一般来说请求参数如下:

参数 是否必须 含义
grant_type 必须 授权类型,此值固定为authorization_code
code 必须 授权码
client_id 必须 客户端标识,一般是第三方分配的AppID
client_secret 必须 客户端密钥,一般是第三方分配的AppSecret
redirect_uri 必须 回调地址,与之前的 redirect_uri保持一致

Access Token Grant:服务器会验证客户端传过来的参数,验证通过后,给客户端返回 access token,一般返回数据格式如下:

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "scope":"user_info"
}
  • access_token:访问令牌
  • token_type:令牌类型
  • expires_in:令牌过期时间
  • refresh_token:刷新令牌
  • scope:权限范围

其中 refresh_token 用于在授权自动续期步骤中,获取新的Access_Token时需要提供的参数。

至此,授权流程全部结束。

隐式授权模式(Implicit)

跟前面的Authorization Code模式非常相似,只是省略掉了颁发授权码(Authorization Code)给客户端的过程,而是直接返回访问令牌和可选的刷新令牌。适用于没有Server服务器来接受处理Authorization Code的第三方应用,其整个流程如下:

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+                                         +---------------+
 |         -+----(A)-- User Authorization Request --->|               |
 |  User-   |                                         | Authorization |
 |  Agent  -|----(B)-- User Authenticates ----------->|     Server    |
 |          |                                         |               |
 |          |<---(C)-- Redirect URI With ------------<|               |
 |          |          Access Token                   +---------------+
 |          |            
 |          |                                         +---------------+
 |          |----(D)---Follow  Redirect URI --------->|   Web-Hosted  |
 |          |                                         |     Client    |
 |          |                                         |    Resource   |
 |          |<---(E)-- Send Token Extract Script ----<|               |
 |          |                                         +---------------+
 +-|--------+
   |    |
  (A)  (F) Pass Token to Application
   |    |
   ^    v
 +---------+
 |         |
 |  Client |
 |         |

User Authorization Request:客户端提构造了一个用于请求授权的链接,链接格式大概如下:

https://open.server-name.com/oauth2/authorize
    ?response_type=token
    &client_id=AppID
    &redirect_uri=REDIRECT_URI
    &scope=SCOPE
    &state=STATE

与之前的授权码模式相比,只是将 response_type 换成了 token

User Authenticates:与之前的相同,用户决定是否给客户端授权

Redirect URI With Access Token:用户同意授权,认证服务器将用户重定向到客户端指定的重定向redirect_uri,并在uri的中添加访问令牌,链接格式大概如下:

http://client.name.com/redirect_url/#access_token=2YotnFZFEjr1zCsicMWpAA&token_type=Bearer&expires_in=3600&scope=SCOPE&state=STATE

此时的 token_type 恒为 Bearer

要注意的是,返回值放到了REDIRECT_URIhash 部分,而不是作为 ?query 参数,这样浏览器在访问重定向指定的url时,就不会把这些数据发送到服务器。

Follow Redirect URI:浏览器请求redirect_uri标识的客户端地址,此时不包含hash值,并保留access_token相关信息。

Send Token Extract Script:客户端返回一个包含 token 的页面,页面执行脚本获取 redirect_uri 中的 access_token

Pass Token to Application:浏览器获取的令牌发给客户端。

至此,授权流程全部结束。

密码模式(Resource Owner Password Credentials)

这种模式更加简化,客户端直接使用用户提供的usernamepassword来直接请求获取access_token信息,这种模式一般适用于用户高度信任第三方客户端的情况,其整个流程如下:

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      v
      |    
     (A) Resource Owner Password Credentials From User Input
      |
      v
 +---------+                                            +---------------+
 |         |>--(B)---- Resource Owner ----------------->|               |
 |         |         Password Credentials To Server     | Authorization |
 | Client  |                                            |     Server    |
 |         |<--(C)- Access Token Passed To Application-<|               |
 |         |       (w/ Optional Refresh Token)          |               |
 +---------+                                            +---------------+

Resource Owner Password Credentials From User Input:用户向客户端提供用户名与密码作为授权凭据。

Resource Owner Password Credentials From Client To Server:客户端向授权服务器发送用户输入的授权凭据以请求 access token(要求客户端必须已经在服务器端进行注册),其请求参数主要如下:

参数 是否必须 含义
grant_type 必须 授权类型,此值固定为password
username 必须 用户登陆名
passward 必须 用户登陆密码
scope 非必须 授权范围

Access Token Passed To Application:授权服务器对客户端进行认证并检验用户凭据的合法性,如果检验通过,将向客户端返回 access token

至此,授权流程结束。

客户端模式(Client Credentials)

这是最简单的一种授权模式,客户端直接已自己的名义,而不是用户的名义直接去授权服务器发起授权,获取 access_token,流程也是非常简单:

 +---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

Client Authentication:客户端直接发起授权请求,此时的 grant_type 的值为 client_credentials

Access Token:认证服务器确认身份后,向客户端发放 access_token

小试牛刀

鉴于授权码模式(Authorization Code) 是目前来说使用最为广泛,流程也时最为最完整、流程最严密的一种授权模式,我以它为例采用的 koa2 ,自定义实现了一个简单的 Authorization Code 授权方案。

主要思路还是通过 client 引导用户点击进入授权页,在授权页面通过确认授权后向后端请求 code , 重定向到 redirect_uri , 并获取服务端的 code,之后通过 code 再去请求服务端获取令牌 access_token,之后通过 access_token 获取用户信息,整个效果如下图所示:

这里我就不放代码了,容易占地方!!详情请戳这里

pass:代码里面的数据都是mock的,还是有较多异常case未做处理,旨在了解整个授权的过程和实现思路,仅供参考

结语

OAuth 的授权方案目前已经非常成熟,在整个互联网行业中也是非常常见的,早些年做微信生态的公众号的时候初次接触,到现在的深入了解,也算是一些进步吧。

希望阅读完本文也能让你对 OAuth 授权有一个深刻全面的认识,或者是加深巩固 OAuth 授权方面的知识。

理论结合实践,才是我们做为coder应有的追求,加油!

参考资料及文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345