早期登录模式 Link to heading

Login

早期网站自己建立一套密码登录机制。将用户的 Username 和 Password 保存在自己的数据库中,使用哈希算法对密码进行加密。利用 cookie 记录登录状态。

缺点: Link to heading

  • 安全:一个小网站想要保证数据安全非常困难
  • 维护:维护成本高,需要不断跟进最新技术(旧的加密算法随着算力提高已经失效了)

OAuth 2.0 和 OpenID Connect 是目前业界解决这一问题的最佳尝试。

OAuth 2.0 Link to heading

OAuth 2.0 具有很多关键词和术语,互联网上的很多介绍对其描述不明,这里尝试简单梳理一下。

OAuth 之前 Link to heading

在 2010 年之前,登录的方式非常有限:

  • 简单登录:利用账号名和密码登录,使用 cookies 记录存储状态
  • SSO:利用 SAML 协议实现跨域登录(一般是同一个网站/公司的不同域名服务)。

这两种登录方式无法解决以下问题:

  1. 移动应用登录:移动应用对于 cookies 不能保留(除非重新实现一套浏览器的逻辑)。但是又需要保留一些登录状态。
  2. 委托授权:如果需要其他应用授权当前应用,没有方式做到。(Yelp 曾经要求用户输入其他网站的账户密码,以此进行朋友推荐等,类似手机上的请求访问通讯录)。

OAuth 委托授权 Link to heading

假设用户相信 Yelp.com,想请他访问自己 Gmail 账号中的联系人以邀请他们。此时 yelp.com 中就会有一个按钮“使用 Google 登录“。

Connect with Google

点击按钮后,用户进入 Google 登录页面,当然如果浏览器已经保存了相关的 cookies 等,就会直接进入登录后的流程。这里假设浏览器第一次访问 Google,用户在其中输入 Google 的账号密码进行下一步。

Google Account Login

在登录完毕后,在 Google 的页面中会询问用户是否授权 yelp 访问 Google 的资源。

Authorization Window

当用户允许授权之后,浏览器就会回调到 yelp 中继续,yelp 也拥有了访问资源的权限。

Callback

OAuth 2.0 术语 Link to heading

  • Resource owner: 资源的所有者,就是那个要点击 Yes 按钮的人,换句话说就是使用 OAuth 服务的每一个人。
  • Client: 客户,就是想要获得资源权限的角色,通常是一个网站(比如上面的 yelp)。
  • Authorization Server: 授权服务器:用于询问用户是否授权的服务器。
  • Resource Server: 资源服务器:用于为已经得到授权的客户提供资源的服务器。
  • Authorization Grant: 授权授予,指上面的一整套用于让用户同意客户访问资源的过程。
  • Access token: 访问令牌,是客户在请求资源服务器的时候,需要提供的令牌。
  • Redirect URI: 重定向 URI,URI 是统一资源定位符,一般来说就是个 URL,是在授权服务器完成授权流程之后,重定向到的服务器,一般指向客户的某个 API。

Client 既不是浏览器 CS 架构里面的 客户端,也不是那个点击 允许 按钮的用户。Client 指的是想要从 授权服务器 拿到数据的一方,在上面的流程中指的是 yelp.com。这一点很重要不要搞混了。

带着术语重新看 OAuth 2.0 流程 Link to heading

有了以上的术语,我们可以重新审查一下上面的过程:

Go to authorization server

第一步是从 yelp.com 进入 account.google.com 进行授权的过程,从客户(Client)到授权服务器(Authorization Server)。在访问授权服务器的同时,还提供了两个信息:

  1. Redirect URI,在这一步就需要让授权服务器直到回调的URI是什么,这样在用户授权之后才知道怎么将授权的信息返回给客户。
  2. Response type,指的是授权的类型,OAuth 2.0 定义了多种授权类型,后面展开讨论。这里只需要记住使用的是 code 模式就行了。

第二步是用户登录 Google 账户,并且点击允许的过程,这一步都是在授权服务器上进行,并没有太多事情发生。

Authorization Server Authorization Server

第三步是允许之后,页面重定向的过程。

Redirect

此时页面会被重定向到 客户的某个 API,并且会将一个 code 交给客户。刚刚提到了我们授权的过程使用的是 code 模式,就指的是这个授权码。需要注意的是,客户拿到的授权码,并不是我们最终的访问令牌,因为 授权码模式不希望用户获得访问令牌,只希望客户获得。我们会在后面继续解释。

第四步是客户请求访问令牌的过程。第三部的授权码通过重定向 URI 被发送给了客户,客户需要拿着这个 code 向授权服务器要访问令牌 access token。

Code to access token

授权服务器验证了 code 的有效性之后,返回给客户一个 access token,此时客户终于拥有了资源的访问权限。

第五步就是客户访问资源服务器的过程了,客户向资源服务器发送一个请求,并同时携带着访问令牌 access token,最终实现了授权获取过程。

OAuth 2.0 workflow

Scope 访问域 Link to heading

刚刚我们解决了授权授予的过程,但是没有解决对权限的细粒度分类。比如我们只希望授权客户一小部分资源的权限,这时候我们需要访问域来确定不同客户的权限。

加入访问域

比如这里我们只向授权服务器请求了 profile 和 contacts 两个权限,之后客户拿到的访问令牌也只能访问这两种资源。

为什么要用 code 换 access token? Link to heading

1https://accounts.google.com/o/oauth2/v2/auth?
2	client_id=abc123&
3	redirect_uri=https://yelp.com/callback&
4	scope=profile&
5	response_type=code&
6	state=foobar

还记得我们刚刚提到,在访问授权服务器的时候,我们指定了参数 response_type=code 来使用 code 模式,这样授权服务器返回的就是一个 code,随后客户端用 code 换 access token。

了解了上面的流程之后,我立刻对 code 的使用产生了疑问:为什么要用 code 来换 access token 呢,当 google 的授权服务器得到了用户的确认,直接把 access token 附加在 redirect url 上面不就行了吗?

我们这里区分一下前端和后端(这里作者用了 Front Channel 和 Back Channel)。用户(Resource owner)属于前端,客户(Client)属于后端。对于用户来说,他们通常在“不安全”的环境中进行授权,比如他们使用浏览器进行访问,他们可能在一个流量被监听的环境当中登录了网站。这种前端的环境是各方都不信任的环境。此时如果 google 的授权服务器把 access token 发给了用户,那么就有可能泄露这个令牌,最终被其他人所利用。

因此我们借助这个 authntication code。先把 code 通过 redirectURL 发给客户,让客户再和授权服务器进行沟通拿到最终的 access token。可以想象客户和授权服务器的沟通之间还会有一些基本的验证,这样授权服务器才确认真正是客户发来的请求,而不是其他伪造者。

Authorization code flow

客户和授权服务器怎么确认彼此身份 Link to heading

刚刚提到了,授权服务器需要确认是客户(Client)拿着授权服务器发给用户的 code 来换 access token。那么授权服务器是怎么确认客户的身份呢?答案就是利用 client_id 和 client_secret。

在客户(Client)向授权服务注册用户时,可以获得 client_id 和 client_secret。在向授权服务器请求访问令牌的时候,只需要将 code, client_id 和 client_secret 共同发送给授权服务器即可。

OAuth 2.0 模式 Link to heading

  • authorization code 模式:前端拿到 code 发给 client,之后 client 发给 authorization server。最完整的验证方式。
  • implicit 模式:只有前端时,如静态页面等没有后端服务时可用。将 reponse type 替换为 token,前端直接用 token 和资源服务器交换信息即可,需要前端尽可能安全。

OAuth 不应当用于身份验证——OpenID Connect Link to heading

在 OAuth 的整个流程中,其实用户的 “身份” 并没有被规定。换言之,在整个 OAuth 过程当中,并没有告诉客户(Client)这个验证的用户到底是谁。因此绝大部分流行的公司提供的 OAuth 服务都需要额外定义一些方式来告知客户当前用户的身份。

这就给了 OpenID Connect 发光发热的机会。Open ID 仅仅是在 OAuth 2.0 基础上添加了一系列内容:

  • ID token
  • Userinfo endpoint ofr getting more user information
  • Standard set of scopes
  • Standardized implementation