OAuth协议与第三方登录

正好正在写博客的登录模块,开始考虑要不要自己弄一套用户系统,但是后来想了很久,还是决定不弄了,因为博客的用户系统作用其实不是很大,其功能主要是为评论系统服务的,相对的,如果是社交网站,用户的主页与交流等功能就很重要了。在这种需求下,使用第三方登录来作为博客的用户体系还是比较合适的。这篇文章将详细介绍如何使用基于OAuth协议的第三方登录API。

什么是 OAuth?

先来看一下Wikipedia是怎么说的吧。

OAuth is an open standardfor access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.

说的是 OAuth 是一个开放的访问授权标准,用户能通过这个协议准许第三方网站或应用访问他们在另一个网站储存的私密资料,在这个过程中,用户只需在储存资料的网站中验证自己,而无需将密码提供给第三方网站。

OAuth 2.0协议是现在用的最多的访问授权标准,它虽然与OAuth 1.0同出一门,但是OAuth 2.0并不兼容OAuth 1.0,所以这里主要介绍OAuth 2.0协议。

OAuth 2.0协议的认证过程

OAuth 2.0协议涉及到了三个角色:

  • 服务提供方:即用户资料的存储方,比如GitHub、QQ等
  • 客户端:即需要使用提供方资料的网站或者应用,比如我的博客
  • 用户:将资料存放在提供方的受保护的资料拥有者,比如GitHub用户、QQ用户

OAuth 2.0协议的认证和授权过程如下:

  1. 客户端向服务提供方请求一个客户端ID(Client ID)和客户端秘钥(Client Secret),两者是用于服务提供方验证客户端身份的,如果服务提供方允许,则客户端即可获得第三方登录的权限(通常在各大服务提供方的开发者页面,如QQ开放平台,GitHub Developer,QQ等平台的请求相对麻烦)
  2. 用户在客户端使用第三方登录功能
  3. 客户端引导用户跳转到服务提供方的授权页面请求用户授权(如你们常见的QQ登录授权页面)
  4. 用户在授权页面使用其登录信息登录服务提供方并且授权客户端使用Ta的信息(比如你们常见的QQ空间询问是否允许XX网站使用你的XX资料)
  5. 服务提供方返回一个授权码(Code),并且将用户重新引导的客户端的后台(这是一个回调网站,是可以自己设置的,自己写一个Http响应,并且将这个Http响应的地址填到API的回调地址中)
  6. 客户端后台使用授权码(Code)和客户端ID(Client ID)、客户端秘钥(Client Secret)等参数一同向服务提供商请求一个访问令牌(Access Token)
  7. 服务提供商返回访问令牌(Access Token),客户端使用这个访问令牌再次调用服务提供商的API即可获取到用户的信息

实战

因为我的博客是使用Django(Python3)写的,所以这里的例子就使用Django(Python3)来操作,服务提供方的话,使用GitHub举例,其他的平台可能大同小异,只不过GitHub想要申请客户端身份的话最简单。我们可以一边看着GitHub的API一边操作:https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/

第一步,当然是在GitHub上申请一个OAuth Apps,也就是申请扮演客户端角色:

打开网址 https://github.com/settings/developers,依次选择OAuth Apps -> New OAuth App,前几个可以随意填写,最为重要的是Authorization callback URL一栏,这一条要填写你自己为第三方登录这一过程的Http响应地址,你可以先占一个坑,反正后面也能改,我们也可以在Http响应(在Java Web中即Servlet,在Django中即view函数)写完之后再填写这一栏。

完成填写之后,打开你刚刚新建的OAuth App,即可看到你成功申请了一个客户端身份,在这个页面中你即可看到之前说的Client ID 和 Client Secret,用这俩即可向服务提供商说明你客户端的身份。

接着把你登录页面中的第三方认证入口的链接(即我们常见的使用QQ登录、使用GitHub登录按钮)设置为API中说的用户授权页面( https://github.com/login/oauth/authorize ),同时还要按照API中的要求使用?key1=value1&key2=value2的形式拼凑成GET参数传递给这个URL。API中要求的必须字段是Client ID,那么最终的链接应该是上面说的用户授权页面URL加上?client_id=XXXXXXXXX。

如果链接无误的话,用户点击链接之后应该能够成功跳转到GitHub的授权页面,GitHub会询问你是否要授权客户端使用你的资料,你可以用这一个办法验证上一步是否正确。当用户同意授权之后,GitHub会跳转到你之前填的那个Authorization callback URL去,并且在GET参数中带上一个code,那么接下来,就是服务器后台的事情了。

我们在之前已经为Authorization callback URL占了一个坑了,比如我的是 http://www.kindemh.cn/login/github,这个地址是你的服务器用于处理第三方登录的Http响应(Java Web是Servlet,Django是view函数)的地址,你之前填上去了也能改,接下来我们开始写对应的view函数。

首先在url.py中填写这个新增的url

# Blog/main/urls.py

urlpatterns = [
    ...
    # github登录 # 你的 Authorization callback URL 需要填写的地址
    url(r'^login/github$', views.login_github, name='login_github')
]

然后在views.py中新写一个view函数,用于处理GitHub登录

# Blog/main/views.py

...
# github 登录回调
def login_github(request):
    # 获取 code
    code = request.GET.get('code')
    # 准备POST参数用于交换 access_token,即上面说的获取访问令牌,这里要用到 client_id 、client secret 和 code
    data = bytes(urllib.parse.urlencode({
        'client_id': github_client_id,
        'client_secret': github_client_secret,
        'code': code
    }), encoding='utf8')
    # 发送Http请求用于交换
    access_token response = urllib.request.urlopen('https://github.com/login/oauth/access_token', data=data)
    # 提取 access_token
    access_token = str(response.read(), encoding='utf-8').split('&')[0].split('=')[1]
    # 使用 access_token 获取用户信息
    response = urllib.request.urlopen('https://api.github.com/user?access_token=' + access_token)
    # 解码成Python对象
    user_info = json.loads(response.read().decode('utf-8'))

    # TODO 接下来对你的数据库进行需要的操作,并且将用户登录信息存入 session,并且在最后重定向到登录之前的页面

在这个响应中,按照OAuth 2.0协议的步骤和GitHub的API写了,注释很清楚,很容易明白,这里要注意的是在GitHub访问的第二步(API中的step II),请求的方式是POST,大家一定要注意,很多人出问题都是在这里。最后的TODO中,大家可以根据需求对自己的数据库进行操作,也可以在session中保存用户登录信息,最后再重定向以完成第三方登录。

这样一来,第三方登录就完全完成了,其实也不难,如果还是有失误,请从上面开始认真按照步骤执行,还有问题请在评论上指出,我将会积极回复(T-T博客上的评论功能还没做,等我做好了再指出吧)。