React + Node 單頁應用「二」OAuth 2.0 受權認證 & GitHub 受權實踐

關於項目

項目地址
預覽地址html

記錄最近作的一個 demo,前端使用 React,用 React Router 實現前端路由,Koa 2 搭建 API Server, 最後經過 Nginx 作請求轉發。前端

文章列表

第一篇:React + Node 單頁應用「一」前端搭建
React + Node 單頁應用「二」OAuth 2.0 受權認證 & GitHub 受權實踐node

這是第二篇,介紹下 OAuth 2.0 受權機制,以及 Github App 受權過程,經過獲取受權使用 Github API。react

OAuth 2.0

背景

傳統的 CS(Client-Server) 受權模式下,請求訪問受保護資源(用戶信息)時,客戶端須要向服務器提供資源所屬用戶的證書,爲了讓第三方拿到資源,用戶就得將證書共享給第三方應用。這種方式會致使如下幾個問題:ios

  • 第三方應用爲了長久使用,須要存儲用戶憑證,特別是明文密碼。
  • 服務器須要支持密碼驗證,雖然密碼存在固有的安全缺陷。
  • 第三方應用得到了資源的過分訪問權限,致使用戶在受權期間沒法限制第三方應用的訪問權限。
  • 用戶不能單獨撤銷某一個第三方應用的訪問權限,必須修改密碼來撤銷全部受權應用的權限。
  • 任何第三方應用的漏洞均可能會威脅到用戶的密碼,以及對應密碼權限下的用戶數據。

爲了解決這個問題,OAuth 協議引入了受權層,並將客戶端與服務端的角色區分開,在 OAuth 協議中,客戶端請求的 Access_token ,被託管在資源服務器,但受用戶控制,而且與用戶憑證徹底不一樣。git

舉個例子,如今有一家檔案館,檔案館中有不少資料,這些資料有些涉及到機密,有些只是常規資料,
研究員小李須要進入檔案館查找常規資料,因而就跑去找羅館長批條子github

「館長館長,我要看一些常規資料,請給我批個條子吧」json

羅館長了解了小李要看的是常規資料,很爽快地批了axios

「沒問題,給,這是贊成的條子跨域

因而小李拿到條子後,徑直去找了檔案館門衛大壯

「大壯,你看這是館長給我批的查看常規資料的條子」,

大壯確認沒問題後

「嗯,既然館長贊成了,來,這是常規文件的鑰匙」,

因而小李就拿着鑰匙進入館內開始找資料,館內每道房門都有一把鎖,若是房間裏存放的是常規資料,小李只須要出示鑰匙就能夠進入,但存放涉密資料的房間,小李的鑰匙打不開,必須找館長批一份查看涉密資料的條子,再拿着條子去大壯那兒換一把新的鑰匙。

角色

  • 資源全部者
    能夠受權獲取受保護資源的實體,若是這個全部者是人,即常規意義的用戶,即檔案館的館長。

  • 資源服務器
    資源服務器用於存儲資源,接收請求,並將資源返回給攜帶合法 Access_token 的請求,在咱們的例子中,檔案館就充當着資源服務器的角色。

  • 客戶端
    獲取受權後,表明用戶請求資源的第三方應用,也就是故事中的研究員小李。

  • 受權服務器
    受權服務器在成功認證資源擁有者並獲取到受權後,發放 Access_token 給客戶端,故事中大壯的主要工做內容就是發放訪問鑰匙。

受權流程

/**
 * 協議流程
 * 引自 RFC6749
 */
 +--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

上面這張圖是引自 RFC6749 的 OAuth 2.0 受權流程,歸納以下

  • 第三方應用向用戶請求受權,用戶贊成,第三方拿到受權許可
  • 第三方拿着用戶的受權許可,請求受權服務器要訪問數據的 Access_token
  • 帶着 Access_token,第三方應用就能夠訪問資源服務器中的資源了

Github APP 受權

Github 有一套很是完備的 API,在沒有得到受權時,僅有部分功能可用,例如獲取用戶信息、倉庫信息等,而且調用次數被限制在了一小時僅容許60次

受權後,每小時調用次數放開到每小時 5000 次,而且申請受權時,能夠選擇申請的權限範圍,例如申請 star 項目、follow 用戶的權限(咱們此次用到的)等等,咱們經過申請完整的權限,甚至能夠寫一個 Github 第三方應用。

註冊並配置 APP

要獲取受權,首先須要在 Github 註冊一個應用,註冊這個應用以前,須要準備好兩個東西

  • 用做應用主頁訪問的 URL,例如 github.lijundong.com(若是是 IP 記得加協議頭)
  • 用做接受受權回調的的 URL,例如github.lijundong.com/github/getauth

可依照如下路徑建立一個受權 APP,

Github > setting > Developer settings > OAuth Apps > New OAuth App

進入註冊界面,須要填寫應用名、應用主頁地址、應用簡介、以及回調 URL,建立完成會跳轉到 APP 管理頁面,在管理頁面能夠更新應用信息以及上傳應用 Logo,而且你將看到你的應用的 Client IDClient Secret,接下來獲取權限須要用到這兩個東西。

受權流程

GitHub 官方的受權流程:
第一步:頁面跳轉到 GitHub 的受權頁

在項目中,我選擇了用 <a> 標籤連接的方式跳轉。

GET https://github.com/login/oauth/authorize?client_id=xxx&scope=xxx
/**
 * client_id:註冊應用的 client_id 必填
 * scope:申請的權限範圍  選填,默認用戶權限爲空
 */

第二步:回調接口收到 GitHub 的回調請求,得到 code

咱們在註冊應用時,設置了受權回調 URL,上一步中,Github 受權頁成功得到用戶受權後,會帶上 code 請求咱們設置的回調 URL,在這一步中,咱們的 Server 就拿到了用戶的受權 code。

第三步:經過 code 獲取 Access_token

最後一步經過已有 code,加上應用的 client_idclient_secret,咱們向 Github 申請 Access_token

/**
 * code:第二步獲取到的 code 必填
 * client_secret:註冊應用 client_secret 必填
 * client_id:註冊應用的 client_id 必填
 */
const rp = require('request-promise')
let option = {
    uri: 'https://github.com/login/oauth/access_token?client_id=' + clientId + '&client_secret=' + clientSecret + '&code=' + code,
    json: true
} 
let tokenResp = await rp(option);

第四步:將得到的 Access_token 寫入頁面的 cookie 中。

ctx.cookies.set('access_token', tokenResp.access_token, {
    'httpOnly': false
})

獲取用戶數據

由於第三方應用請求 GitHub API 涉及到跨域,第一篇文章已經提到了Github API 只支持 XHR 跨域請求,一些同窗若是用 Fetch 跨域請求 API 會致使請求 request type 變成 option ,因此項目中改用 axios 進行網絡請求。

這裏以獲取登錄用戶基本信息爲例,

import Axios from 'axios'
import Cookie from 'js-cookie'
const access_token = Cookie.get('access_token');

getLoginInfo() {
    let that = this;
    let url = API.GITHUB.GET_LOGIN_INFO;
    Axios.get(url, {
        params: {
            access_token: access_token
        }
    }).then(function(res) {
        that.setState({
            loginInfo: res.data,
        });
    }).catch(function (error) {
        console.error(error);
      })
}

參考:

相關文章
相關標籤/搜索