需求:在网站上想评论一篇文章,而评论文章是要用户注册与登陆的,那么怎么免去这麻烦的步骤呢?答案是经过第三方受权登陆。本文讲解的就是 github 受权登陆的教程。html
效果体验地址:http://biaochenxuying.cn前端
先来看下 github 受权的完整流程图 1:node
或者看下 github 受权的完整流程图 2:react
首先咱们必须登陆上 github 申请一个 OAuth App,步骤以下:git
具体实践以下:github
流程也可看 GitHub 设置的官方文档-Registering OAuth Apps。web
github 文档:building-oauth-apps/authorizing-oauth-apps数据库
受权登陆的主要 3 个步骤:express
笔者此次实践中,项目是采用先后端分离的,因此第 1 步在前端实现,而第 2 步和第 3 步是在后端实现的,由于第 2 个接口里面须要Client_secret 这个参数,并且第 3 步获取的用户信息在后端保存到数据库。json
笔者项目的技术是 react。
// config.js // ***** 处请填写你申请的 OAuth App 的真实内容 const config = { 'oauth_uri': 'https://github.com/login/oauth/authorize', 'redirect_uri': 'http://biaochenxuying.cn/', 'client_id': '*****', 'client_secret': '*******', }; // 本地开发环境下 if (process.env.NODE_ENV === 'development') { config.redirect_uri = "http://localhost:3001/" config.client_id = "******" config.client_secret = "*****" } export default config;
redirect_uri 回调地址是分环境的,因此我是新建了两个 OAuth App 的,一个用于线上生产环境,一个用于本地开发环境。
通常来讲,登陆的页面应该是独立的,对应相应的路由 /login , 可是本项目的登陆 login 组件是 nav 组件的子组件,nav 是个全局用的组件, 因此回调地址就写了 http://biaochenxuying.cn/。
// login.js // html <Button style={{ width: '100%' }} onClick={this.handleOAuth} > github 受权登陆 </Button> // js handleOAuth(){ // 保存受权前的页面连接 window.localStorage.preventHref = window.location.href // window.location.href = 'https://github.com/login/oauth/authorize?client_id=***&redirect_uri=http://biaochenxuying.cn/' window.location.href = `${config.oauth_uri}?client_id=${config.client_id}&redirect_uri=${config.redirect_uri}` }
// nav.js componentDidMount() { // console.log('code :', getQueryStringByName('code')); const code = getQueryStringByName('code') if (code) { this.setState( { code }, () => { if (!this.state.code) { return; } this.getUser(this.state.code); }, ); } } componentWillReceiveProps(nextProps) { const code = getQueryStringByName('code') if (code) { this.setState( { code }, () => { if (!this.state.code) { return; } this.getUser(this.state.code); }, ); } } getUser(code) { https .post( urls.getUser, { code, }, { withCredentials: true }, ) .then(res => { // console.log('res :', res.data); if (res.status === 200 && res.data.code === 0) { this.props.loginSuccess(res.data); let userInfo = { _id: res.data.data._id, name: res.data.data.name, }; window.sessionStorage.userInfo = JSON.stringify(userInfo); message.success(res.data.message, 1); this.handleLoginCancel(); // 跳转到以前受权前的页面 const href = window.localStorage.preventHref if(href){ window.location.href = href } } else { this.props.loginFailure(res.data.message); message.error(res.data.message, 1); } }) .catch(err => { console.log(err); }); }
笔者项目的后端采用的技术是 node.js 和 express。
// app.config.js exports.GITHUB = { oauth_uri: 'https://github.com/login/oauth/authorize', access_token_url: 'https://github.com/login/oauth/access_token', // 获取 github 用户信息 url // eg: https://api.github.com/user?access_token=******&scope=&token_type=bearer user_url: 'https://api.github.com/user', // 生产环境 redirect_uri: 'http://biaochenxuying.cn/', client_id: '*****', client_secret: '*****', // // 开发环境 // redirect_uri: "http://localhost:3001/", // client_id: "*****", // client_secret: "*****", };
// 路由文件 user.js const fetch = require('node-fetch'); const CONFIG = require('../app.config.js'); const User = require('../models/user'); // 第三方受权登陆的用户信息 exports.getUser = (req, res) => { let { code } = req.body; if (!code) { responseClient(res, 400, 2, 'code 缺失'); return; } let path = CONFIG.GITHUB.access_token_url; const params = { client_id: CONFIG.GITHUB.client_id, client_secret: CONFIG.GITHUB.client_secret, code: code, }; // console.log(code); fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(params), }) .then(res1 => { return res1.text(); }) .then(body => { const args = body.split('&'); let arg = args[0].split('='); const access_token = arg[1]; // console.log("body:",body); console.log('access_token:', access_token); return access_token; }) .then(async token => { const url = CONFIG.GITHUB.user_url + '?access_token=' + token; console.log('url:', url); await fetch(url) .then(res2 => { console.log('res2 :', res2); return res2.json(); }) .then(response => { console.log('response ', response); if (response.id) { //验证用户是否已经在数据库中 User.findOne({ github_id: response.id }) .then(userInfo => { // console.log('userInfo :', userInfo); if (userInfo) { //登陆成功后设置session req.session.userInfo = userInfo; responseClient(res, 200, 0, '受权登陆成功', userInfo); } else { let obj = { github_id: response.id, email: response.email, password: response.login, type: 2, avatar: response.avatar_url, name: response.login, location: response.location, }; //注册到数据库 let user = new User(obj); user.save().then(data => { // console.log('data :', data); req.session.userInfo = data; responseClient(res, 200, 0, '受权登陆成功', data); }); } }) .catch(err => { responseClient(res); return; }); } else { responseClient(res, 400, 1, '受权登陆失败', response); } }); }) .catch(e => { console.log('e:', e); }); };
至于拿到 github 的用户信息后,是注册到 user 表,仍是保存到另一张 oauth 映射表,这个得看本身项目的状况。
从 github 拿到的用户信息以下图:
最终效果:
参与文章:
第三方受权登陆的时候,第三方的用户信息是存数据库原有的 user 表仍是新建一张表呢 ?
答案:这得看具体项目了,作法多种,请看下文。
第三方受权登陆以后,第三方用户信息通常都会返回用户惟一的标志 openid 或者 unionid 或者 id,具体是什么得看第三方,好比 github 的是 id
第一种:若是网站 没有 注册功能的,直接经过第三方受权登陆,受权成功以后,能够直接把第三的用户信息 注册 保存到本身数据库的 user 表里面。典型的例子就是 微信公众号的受权登陆。
第二种:若是网站 有 注册功能的,也能够经过第三方受权登陆,受权成功以后,也能够直接把第三的用户信息 注册 保存到本身数据库的 user 表里面(可是密码是后端自动生成的,用户也不知道,只能用第三方受权登陆),这样子的第三方的用户和原生注册的用户信息都在同一张表了,这种状况得看本身项目的具体状况。笔者的博客网站暂时就采用了这种方式。
现实中不少网站都有多种帐户登陆方式,好比能够用网站的注册 id 登陆,还能够用手机号登陆,能够用 QQ 登陆等等。数据库中都是有映射关系,QQ、手机号等都是映射在网站的注册 id 上。保证无论用什么方式登陆,只要去查映射关系,发现是映射在网站注册的哪一个 id 上,就让哪一个 id 登陆成功。
创建一个 oauth 表,一个 id 列,记录对应的用户注册表的 id,而后你有多少个第三方登录功能,你就创建多少列,记录第三方登录接口返回的 openid;第三方登录的时候,经过这个表的记录的 openid 获取 id 信息,若是存在经过 id 读取注册表而后用 session 记录相关信息。不存在就转向用户登录/注册界面要用户输入本站注册的帐户进行 openid 绑定或者新注册帐户信息进行绑定。
具体代码实践请参考文章:
笔者的 github 博客地址:github
若是您以为这篇文章不错或者对你有所帮助,请给个赞或者星呗,你的点赞就是我继续创做的最大动力。