需求:在網站上想評論一篇文章,而評論文章是要用戶註冊與登陸的,那麼怎麼免去這麻煩的步驟呢?答案是經過第三方受權登陸。本文講解的就是 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
若是您以爲這篇文章不錯或者對你有所幫助,請給個贊或者星唄,你的點贊就是我繼續創做的最大動力。