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


關於項目

項目地址
預覽地址

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

文章列表

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

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

OAuth 2.0

背景

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

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

為了解決這個問題,OAuth 協議引入了授權層,並將客戶端與服務端的角色區分開,在 OAuth 協議中,客戶端請求的 Access_token ,被托管在資源服務器,但受用戶控制,並且與用戶憑證完全不同。

舉個例子,現在有一家檔案館,檔案館中有很多資料,這些資料有些涉及到機密,有些只是常規資料,
研究員小李需要進入檔案館查找常規資料,於是就跑去找羅館長批條子

“館長館長,我要看一些常規資料,請給我批個條子吧”

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

“沒問題,給,這是同意的條子

於是小李拿到條子后,徑直去找了檔案館門衛大壯

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

大壯確認沒問題后

“嗯,既然館長同意了,來,這是常規文件的鑰匙”,

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

角色

  • 資源所有者
    可以授權獲取受保護資源的實體,如果這個所有者是人,即常規意義的用戶,即檔案館的館長。

  • 資源服務器
    資源服務器用於存儲資源,接收請求,並將資源返回給攜帶合法 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);
      })
}

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM