本文来自于公众号链接: 最简洁实现Github登录的JS代码示例
公众号中文章格式更加良好,图片不丢失。
本文源码地址: https://github.com/Spring-Security-China/oauth2-client-login-js-github
大纲
- 概述
- 实现思路分析
- 在Github官网注册OAuth2应用
- 代码实现
- 运行与演示
- 总结
概述
本来希望使用纯前端的方式,只一个html在浏览器中运行,最简洁地就能实现Github登录。但是理想很丰满,现实很骨感,实际上需要前端和后端相互配合才可以。
Github的第三方登录功能背后的技术实际上是OAuth2协议,OAuth2协议本身有一定的复杂性,简单理解的话,可以参考公众号文章《OAuth2核心协议概览》。
图片[]
我们知道OAuth2协议中有OAuth2的授权服务器、资源服务器、OAuth2客户端和用户代理这四个角色主体。与其他很多互联网平台如微信公众平台、Google、OKTA等等一样,Github同时是OAuth2授权服务器和资源服务器。用户代理是指的浏览器。那么本文写的代码示例,实际上就是一个OAuth2客户端。
本文希望不需要了解OAuth2协议的细节,仅仅通几行js代码,就可以实现接入Github登录。从而可以先直观地感受下OAuth2核心协议。
实现思路分析
如何接入Github登录?看官网:https://developer.github.com/apps/building-oauth-apps 获取token需要两个步骤:
- 获取code:浏览器访问 https://github.com/login/oauth/authorize 同时传递回调地址等参数
- 使用code获取token:接收到code后,发起POST请求调用 https://github.com/login/oauth/access_token
感觉很简单,那我们直接写个html页面,在页面中写两个方法不就解决了?类似这样:
functionn getCode(){
window.location.href = "https://github.com/login/oauth/authorize"+...
}
function getToken(code){
...POST "https://github.com/login/oauth/access_token" 获取token...
}
这种方案行不通,首先因为“/oauth/access_token”接口是不允许跨源访问的,关于什么是跨源,可以参考《彻底理解浏览器同源策略SOP》和《彻底掌握CORS跨源资源共享》两篇文章。也就是说浏览器无法调用这个接口。其次,如果浏览器可以调用这个接口获取token,那么意味着token暴露在了浏览器端,增加了token泄露的风险。实际上根据OAuth2协议来看,通过code交换token的这个接口流程属于后端流程(“back-end flow”),是后端调用的。
那么只好增加复杂性,引入后端,实现一个前端+后端的最简洁的Github登录的JS代码示例。
图:[silu.png]
在Github官网注册OAuth2应用
首先需要在Github官网注册一个新的OAuth2应用,地址是:https://github.com/settings/applications/new
- Application Name:必填,应用名称, 设置为:
oauth2-client-login-js-github
。 - Homepage URL: 必填,应用主页,设置为
http://localhost:8080
。 - Application description: 选填,应用的说明,置空即可。
- Authorization callback URL: 必填,OAuth认证的重定向地址,本地开发环节可设置为
http://localhost:8080
。
当用户通过浏览器成功登录Github,并且用户在批准页(Approva Page)授权允许注册的客户端应用访问自己的用户数据后,Github会将授权码(code)通过重定向方式传递给客户端应用。
填写无误后,点击Register appcation
按钮,注册成功后,获取到clientId
和clientSecret
。
代码实现
源码地址: https://github.com/Spring-Security-China/oauth2-client-login-js-github
插图:jiegou.png
- server.js是nodejs实现的后端
- loginGithub.html是普通html模拟前端
前端示例代码loginGithub.html
...
<p>
<button onclick="handleLoginGithub()">使用Github登录</button>
</p>
</body>
<script>
//触发github登录
function handleLoginGithub() {
window.location.href = "https://github.com/login/oauth/authorize" +
"?client_id=1994fe209a6970a75a26" +
"&redirect_uri=http://localhost:8080" +
"&scope=" +
"&state=" +
"&allow_signup=" +
"&login="
}
</script>
...
只有一个按钮,点击按钮,重定向到github请求code接口。参数有:
- client_id:注册OAuth2 App成功后的OAuth2客户端Id
- redirect_uri:重定向地址,与注册OAuth2 App的重定向地址保持一致。
- scope是申请访问的权限范围,传空,默认。
- state、allow_signup、login我们都默认传空值
参数具体意义请参考官网:https://developer.github.com/apps/building-oauth-apps
后端示例代码server.js
...略
const server = http.createServer(function (req, res) {
res.writeHeader(200, {
'content-type': 'text/html;charset="utf-8"'
});
if (req.url === "/loginGithub.html") {
//返回页面
fs.readFile("./loginGIthub.html", function (err, data) {
res.write(data);
res.end();
})
} else {
//获取回调地址中的code
const arg = url.parse(req.url, true).query;
//回调地址接收code
if (arg && arg.code) {
//如果参数中有code,说明是GitHub通过重定向传递code,携带code请求token
const tokenUrl = "https://github.com/login/oauth/access_token" +
"?client_id=1994fe209a6970a75a26" +
"&client_secret=1c0de0b18501cfa419a250ebe855207ba6d2b6d7" +
"&redirect_uri=http://localhost:8080" +
"&state=" + arg.state +
"&code=" + arg.code;
//1 后台请求github的token
request.post({url: tokenUrl}, function optionalCallback(err, httpResponse, body) {
if (err) {
res.write('<h1>获取token失败</h1><p>' + err + '</p>');
res.end();
} else {
//获取token成功
let token = url.parse('?' + body, true).query.access_token;
//2 使用token访问用户在github的信息
request({url: 'https://api.github.com/user', headers: {
'Authorization': 'token ' + token, //携带token
'User-Agent': 'example' //github要求必须设置User-Agent
}}, function (error, response, b) {
if (error) {
res.write('<h1>获取token成功,但是使用token获取用户信息失败</h1><p>' + body + '</p>');
res.end();
} else {
res.write('<h1>获取token成功,使用token获取用户信息成功</h1><p>' + body + '</p><p>' + b + '</p>');
res.end();
}
})
}
});
} else {
res.write('<h1>404</h1><p>此示例只有 http://localhost:8080/loginGithub.html 一个页面</p>');
res.end();
}
}
}).listen(8080); //端口号
...略
这是nodejs实现的简单后端代码,最重要的逻辑是当请求中携带形如code=xxxx&state=
参数的时候,后端接受到参数中的code,然后使用reqest发起post请求获取token。当获取token成功后,再携带token获取用户信息。
运行与演示
- 安装nodejs环境
- 安装依赖的request工具包:
npm install --save request
- 运行:
node server.js
- 打开浏览器访问:http://localhost:8080/loginGithub.html
- 点击按钮:
使用Github登录
图:fangwen.png
图:shouquan.png
图:chenggong.png
总结
OAuth2客户端逻辑并不复杂,还有其他功能如刷新token等不一一赘述。然后真要实现功能的同时还保证安全性良好是有一定的难度的,因此实际项目一般不会使用这么原始的方式去自己实现OAuth2登录逻辑,而是使用各种第三方工具库。各种编程语言栈都有很多OAuth2类库,都可以便捷地实现GitHub登录,比如在java领域常用的Spring Security框架。
更多干货文章请参考:
《Spring Security实战》