OAuth
网络上关于 Oauth 2.0 协议的基本内容已经很多了,我就不重复写博客了,对基本概念不理解的同学可以先自行 Google。但是我发现实际演示的 demo 很少,所以写了这个偏实战的博客。
本文是以 GitHub 登录为例来演示的。
虽然线上环境肯定要有云服务器,但是可以在本地直接模拟调试的
不需要写一行代码就可以演示一个完成的登录流程!!请读者务必手动的实际操作!!
其实 OAuth 认证说白了:
有三个角色:GitHub,用户,第三方网站 third-side
需要就是完成一件事:经用户同意,让 third-side 安全的从 GitHub 拿到一个 token
third-side 拿到这个 token 可以用来
标识一个用户,读取用户的基本信息
代表用户在 GitHub 上完成一些操作,比如写一个 issue 之类的
基本流程
向 GitHub 申请注册一个 application 一个 application 对应一个项目,我们需要拿到一个 client id 和 secret 来用于后续的登录认证
构造相关的登录链接,引导用户点击登录。这一步需要用到上面的 client id
用户同意登录后,third-side 可以拿到一个 code(后面详细解释),通过这个 code 可以向 GitHub 拿到用户的 token
第一步注册 App
因为我们要使用 GitHub 作为第三方登录,所以肯定要先到官网上注册一个 application。
网址:https://github.com/settings/d…
点击绿色的按钮就行了,点击后会出现???? 的页面:
上面的内容很多,但是只有两个字段是关键的
Application Name:这个是 GitHub 用来标识我们的 APP 的
Authorization callback url: 就是上面我特意用红色字体标识的的,很关键
Homepage url 这个是展示用的,在我们接下来的登录中用不到,随便写就行了
这个 Authorization callback url 就是在用户确认登录后,GitHub 会通过这个 url 来告知我们的服务器。所以真实情况下这个 url 应该是由专门的服务器程序来监听的。但是这一步大家可以先跟着我填写,后面细细说。
完成注册后,我们就有了这些数据:
上面有两个数据很关键
cliendt_id 这是 GitHub 用来标识我们的 APP 的接下来我们需要通过这个字段来构建我们的登录 url
client Secret 这个很关键,等会我们就靠它来认证的,要好好保存。我这个只是演示教程,用完就销毁了,所以直接公开了。
构造 URL
然后我们接下来怎么做?很简单
<body>
<a href=”https://github.com/login/oauth/authorize\
?client_id=47878c9a96cfc358eb6e”>
login with github</a>
</body>
我们只要在我们的登录页面添加这样的代码,引导用户点击我们的登录按钮就行了。
注意到上面的流程了吗?其实我们已经完成了一半的登录认证流程了,然后我们来分析一下。
首先请大家思考一下:从用户点击 login with github 开始,中间有几次重要的 HTTP 报文传递?
不重要是指中间那些网页中附带的对 css js 资源的请求,这些不算。
与登录认证有关的 http 请求有几个?
3 个!!!说清楚这一点,基本就明白了。
首先我们构造了这样的 html 页面
<body>
<a href=”https://github.com/login/oauth/authorize\
?client_id=47878c9a96cfc358eb6e”>
login with github</a>
</body>
关键是我们构造了一个 url
https://github.com/login/oauth/authorize:这一部分是固定的,只要是用 GitHub 登录,就得这样。你可以从官方文档上看到这个链接。
如果是微信登录,twitter 登录,也是大同小异,具体的 url 可以在相关的官网上找到。
GitHub 会监听这个路由,来做出登录处理
然后后面是一个查询字符串 client_id=47878c9a96cfc358eb6e。它的值就是我们之前申请到的 client id。这是个必须存在的字段,用户点击登录后,GitHub 就是通过这个字段来确认用户究竟是想登录哪个网站。
然后 GitHub 会返回这样的页面,确认用户是否真的要登录,这就是第一次 HTTP 请求和响应
然后用户点击确认后发生了什么?
用户点击确认,就是向 GitHub 发送一个报文,确认自己确实到登录某网站
GitHub 收到这个用户的确认消息,然后会返回一个状态码为 301 的报文
这个是第二次 HTTP 请求和响应。因为浏览器收到 301 重定向后,会直接前往新的网址了,所以你要是没仔细看的话,可能就忽略了。
这个状态码为 301 的报文,它的 Location 字段大概长这样:https://github.com/fish56/OAuth?code=a1aee8cacf7560825665>
https://github.com/fish56/OAuth 这个字段就是我们之前填写的 callback url,正常情况下这个应该是我们云服务器的网址。但是这里为了演示方便,我这里就随便填写了我的 github 地址,没关系的
code=a1aee8cacf7560825665:(因为笔者中间调试过,所以现在写的 token 和 gif 里面的不一样哈)
这个就是我们 OAuth 登录的一个核心信息。我之前说过,我们 OAuth 登录的核心目的就是让第三方网站能够安全的拿到用户的 token。用户的浏览器收到之前的 301 的 HTTP 响应后,就会向我们服务器发起请求,请求的同时服务器就拿到了这个 code。服务器就可以通过这个 code 从 github 拿到用户的 token。
这就是第三次 http 请求。
然后又有同学可能会问了,为什么要返回一个 code,而不是直接返回一个 token 呢?
答:为了安全,如果 token 经过用户的手里走一遍,就可能会被其他恶意的人窃取。
OAuth 协议下,GitHub 会返回给用户一个 code,然后用户浏览器通过重定向携带这个 code 来访问我们的服务器,这样服务器就拿到了这个 code。
服务器拿到这个 code 之后,通过结合之前的 client secret 向 GitHub 申请 token,这样会安全一点。
之前我们只做了一半,接下来我们通过 postman 来演示一下如何通过 code 拿到 token。
登录
我们要向 GitHub 申请用户的 token,需要
code 这个只有在用户同意登录后,服务器才能拿到。
client secret + client id
向 https://github.com/login/oauth/access_token 发起 POST 请求,并且携带上面的三个字段这个 url 是 GitHub 规定的,你可以在它的官方文档中找到
然后我们在 postman 中构造这样的请求:
哎,可以看到,这我们确实拿到了用户的 token:
access_token=9094eb58a23093fd593d43eb28c1f06ce7904ed5&scope=&token_type=bearer。
只不过真实情况下上面的操作都是由线上的服务器完成的,我这样操作是方便大家的理解。
代码实战
通过上面的例子我们可以看到,如果只是演示,我们是不需要服务器。接下来我们在本地用代码直接演示下。
演示的代码使用 node + express + request 写的
不过代码很简单,不了解上面的技术栈也可以看得懂
流程上和之前演示的一模一样,只是通过代码来完成
源代码看这里 GitHub
因为我们要启动本地的服务器来监听响应,所以我们首先要修改下我们的 callback URL。
请大家将这个 callback URL 自行修改为 http://localhost:8099/github/login。
这是我们程序的目录结构
然后这是我们的 node 代码:
const querystring = require(‘querystring’);
const express = require(‘express’);
const request = require(‘request’);
const githubConfig = require(‘./oauth.conf’)
let app = express();
// 做一个路由函数,监听 /github/login 的 get 请求
app.get(‘/github/login’, async function(req,res){
//read code from url
let code = req.query.code
// 收到 code 后,向 GitHub 请求用户的 token
request.post(githubConfig.access_token_url, {
form:{
client_id: githubConfig.client_ID,
client_secret: githubConfig.client_Secret,
code: code
}
},function(error, response, body) {
// 正常情况下,返回值应该是形如 access_token=9094eb58a23093fd59
// 3d43eb28c1f06ce7904ed5&scope=&token_type=bearer
// 的字符串,可以通过下面的函数来解析
let result = querystring.parse(body)
// 拿到 token 后,返回结果,表示我们成功了
let access_token = result[“access_token”]
if(access_token == undefined){
res.send(result.error_description)
}
res.send(`You are login! you token is ${access_token}`)
})
})
// 监听 8999,启动程序。注意端口号要和我们之前填写的保持一致
app.listen(8099,function(){
console.log(‘listening localhost:8099’)
})
这是 oauth.conf.js。注意把相关的字段换成你自己的配置。
module.exports = {
client_ID: ‘47878c9a96cfc358eb6e’,
client_Secret: ‘4813689c043c60dbf3d3a0d8e0a984afc0bf810a’,
access_token_url: ‘https://github.com/login/oauth/access_token’
}
这是实际效果
我们的代码做了什么事?只是把我们之前的手动使用 postman 做的事情自动化了
监听 http://localhost:8099/github/login
收到用户浏览器传递的 code 之后,我们的服务器向 GitHub 申请了用户 token
将 token 返回给用户,表明登录成功
不过其实我上面省略了很多操作。正常情况下,服务器拿到用户的 token 后,应该:
把 token 保存到数据库
通过 token 从 github 拿到用户的名称之类的数据
不应该把 token 返回给用户
返回给用户一个 cookie,使得用户保持登录,并且在数据库中把这个 cookie 和用户的 token 对应起来
总结
好了,上面基本就是一次登录流程。
构造登录链接,引导用户登录
用户登录后,GitHub 会把用户重定向到我们预先设置好的 URL,同时携带一个 code
服务器拿到这个 code,向 GitHub 申请 token
拿到 token 后,服务器就可以确认用户成功登录了,然后可以返回一个登录成功的页面