Github OAuth第三方接入

認證和受權

認證(Authentication)

  所謂認證就是如何證實你是你本身的方式,通常來講證實你是你本身的方式就是經過身份證;而互聯網中的認證一般用用戶名和密碼來證實你是你本身。
  經常使用的身份認證的方式通常有:javascript

  1. 用戶名和密碼登錄
  2. 郵箱發送登錄連接
  3. 手機號接收驗證碼

受權(Authorization)

  所謂受權舉個例子就是:你在安裝手機應用的時候,應用會申請權限,你贊成這個權限申請的過程就叫作受權。同理,在oauth中,被受權方會收穫必定的權限。一般使用oauth的廠商有:java

  1. qq
  2. 微信
  3. 微博
  4. 臉書
  5. 推特

受權和認證的關係

  受權並不必定須要認證。好比:當你有鑰匙的時候,你就能夠開門,你並不必定是這個屋子的主人。
  在互聯網中的oauth受權一般來講是經過token進行權限的授予的。ios

OAuth

OAuth的概念

  OAuth是互聯網行業一種標準的受權方式。各個公司根據這一套標準實現本身的OAuth認證和受權流程,而第三方想要接入這個流程,就須要使用OAuth這套方案。
  OAuth目前有兩個版本:git

  1. 1.x(已棄用)
  2. 2.x(流行)

角色理解

  OAuth第三方認證和受權流程中通常會有幾個角色:github

  1. 客戶端:瀏覽器端
  2. 服務端:開發這個網站對應的服務器端
  3. 受權服務器:提供OAuth第三方接入服務的提供商,好比QQ、微信等。

  另一種說法說的是客戶端和服務端能夠合併稱爲被受權方,若是你作的是個純客戶端的OAuth接入的話,那麼你也能夠不須要服務端,不過這個接入方式安全性不高。redis

受權方式

  目前業界的OAuth受權方式有多種,其中最主要的是如下幾種:數據庫

  1. Authorization Code(最經常使用的)
  2. Refresh Token:在受權Token未過時時,向OAuth接入方從新申請一個新的受權Token
  3. Device Code:主要針對智能TV中的第三方接入。
  4. Password:用OAuth提供方暴露的API,進行本網站的用戶名和密碼登錄。(不推薦,信任和安全,通常用於公司內部的登錄方式)
  5. Implicit:已廢棄
  6. Client Credentials

Authorization Code

  經過認證碼的認證受權方式進行OAuth的接入時,主要流程分別是如下幾步:json

  1. 客戶端發起請求redirect到OAuth接入方並附帶上client_id
  2. 用戶在redirect以後的網站上輸入用戶名和密碼
  3. 登錄成功以後,OAuth接入方會返回給服務端一個code
  4. 服務端拿到code以後,拿着client_secretcode向OAuth接入方申請得到Token
  5. 服務端拿到Token以後,進入受權窗口
  6. 受權成功,跳轉到客戶端網站。

Github OAuth第三方接入

註冊Github OAuth

url:https://github.com/settings/appsaxios

  1. 登錄github
  2. 點擊頭像、setting
  3. 點擊developer setting
  4. 新建OAuth App

  注意:HomePage URL在項目未上線以前填本地項目根路徑地址,在項目上線以後填上線以後的域名地址;同理Authorization callback URL在上線以前填寫http://localhost:3000/auth,在上線以後填寫域名+/auth
  註冊成功以後,會出現一個client_id和client_secret,這個兩個值很是重要。須要保存在項目文件之中api

OAuth字段詳解

跳轉字段

url: https://github.com/login/oauth/authorize
param

  1. client_id:client_id是註冊時的client_id
  2. scope:但願獲得的權限範圍
  3. redirect_uri:受權後重定向到項目的地址,必須和註冊Github OAuth App時的redirect uri相同
  4. login:用於登陸和受權應用程序的特定賬戶,若是你在該網站登錄過github並受權,則在此請求OAuth時免去登錄步驟。
  5. state:不可猜想的隨機字符串。它用於防止跨站點請求僞造攻擊。
  6. allow_signup:在OAuth流程中,是否向未認證的用戶提供註冊GitHub的選項。默認值爲true。false在策略禁止註冊的狀況下使用。

請求token

url: https://github.com/login/oauth/access_token
param

  1. client_id:必需,標明token使用方
  2. client_secret:必需,標明token使用方
  3. code:必需,表明用戶已受權
  4. redirect_uri:同上
  5. state:同上

請求用戶信息

url: https://api.github.com/user
header

  1. Authorization:即請求access_token時返回的access_token,該字段統一是token +access_token

Github OAuth受權如何保證安全

策略

  1. 一次性code:即受權成功時返回的code是一次性的,請求access_token後,上一次的code已失效。
  2. id + secret驗證方式:請求access_token時,不只須要code,還須要client_id和client_secret,就算code已泄露,只要對方沒有這兩個字段那他就沒法獲取我方的access_token。
  3. redirect_uri:這個字段是位於請求時返回的uri,若是這個字段與github的Authorization callback URL不一樣時,就會報錯。

使用cookie和session存儲token

  當用戶進行完了OAuth流程以後,將token保存於cookie和session中,這樣用戶就不須要每次切換頁面時都去進行一次OAuth認證和受權。

存儲cookie

koa框架存儲cookie的方式:

server.use(async (ctx, next) => {
  ctx.cookies.set('id', id, {
    httpOnly: true
  });
  await next()
})
複製代碼

存儲session

const session = require('koa-session');
// 給cookie加密,加密以後的密文是jwt
server.keys = ['ainuo develop github apps'];
const SESSION_CONFIG = {
  // key爲存儲的字段名
  key: 'jid',
   // 當沒有配置store時,session是以jwt存儲在cookie中的
   // store:
};
// 使用session中間件
server.use(session(SESSION_CONFIG, server));
// 通常來講咱們會單獨暴露一個接口用於設置session。
router.get('/set/user', async ctx => {
    // 設置session
  ctx.session.user = {
    name: 'ainuo',
    age: 18
  };
  ctx.body = 'set session success'
});
複製代碼

將token存儲到redis

建立Store對象

  建立這個對象的目的是集成redis到kos-session,並簡化redis的相關操做

function getRedisSessionId(sessionId) {
  return `ssid:${sessionId}`
}

module.exports = class RedisSessionStore {
  constructor(client) {
    this.client = client
  }

  // 根據sessionId獲取redis中存儲的session數據
  async get(sessionId) {
    console.log('get session', sessionId);
    const id = getRedisSessionId(sessionId);
    const data = await this.client.get(id);
    if (!data) {
      return null
    }
    try {
      return JSON.parse(data);
    } catch (e) {
      console.log(e)
    }
  }

  // 存儲session數據到redis
  async set(sessionId, sessionValue, lifetime) {
    console.log('set session', sessionId);
    const id = getRedisSessionId(sessionId);
    if (typeof lifetime === "number") {
      lifetime = Math.ceil(lifetime / 1000);
    }
    try {
      const sessionStr = JSON.stringify(sessionValue);
      if (lifetime) {
        await this.client.setex(id, lifetime, sessionStr)
      } else {
        await this.client.set(id, sessionStr)
      }
    } catch (e) {
      console.log(e)
    }
  }

  // 從redis中刪除某個session
  async destroy(sessionId) {
    console.log('destroy session', sessionId);
    const id = getRedisSessionId(sessionId);
    await this.client.del(id)
  }
}
複製代碼

koa-session鏈接redis數據庫

const Redis = require('ioredis');
// 能夠傳入一些配置
const client = new Redis({});
server.keys = ['ainuo develop github apps'];
const SESSION_CONFIG = {
  key: 'jid',
    // 當沒有配置store時,session是以jwt存儲在cookie中的
   store: new RedisSessionStore(client),
   // maxAge: 10 * 1000,
};
// 對服務端的session進行加密
server.use(session(SESSION_CONFIG, server));
// 刪除session時刪除redis數據庫中的ssid:xxx的數據
router.get('/del/user', async ctx => {
    // 設置session
    ctx.session = null;
    ctx.body = 'del session success'
});
// 設置session時添加session到redis數據庫
router.get('/set/user', async ctx => {
  // 設置session
  ctx.session.user = {
    name: 'ainuo',
    age: 18
  };
  ctx.body = 'set session success'
});
複製代碼

koa接入Github OAuth

//config,js
module.exports = {
  github: {
    client_id: 'xxxx',
    client_secret: 'xxxx',
    request_token_url: 'https://github.com/login/oauth/access_token',
    auth_url: 'https://github.com/login/oauth/authorize',
    scope: 'user',
    user_info_url: 'https://api.github.com/user'
  }
};


// auth.js
const axios = require('axios');
const {github} = require('../config');
const {client_id, client_secret, request_token_url} = github;
module.exports = function (server) {
  server.use(async (ctx, next) => {
    if (ctx.path === '/auth') {
      const code = ctx.query.code;
      if (code) {
        // 發起對access_token的請求
        const res = await axios({
          method: 'POST',
          url: request_token_url,
          data: {
            client_id,
            client_secret,
            code
          },
          headers: {
            Accept: 'application/json',
          }
        });
        if (res.status === 200 && !(res.data && res.data.error)) {
          ctx.session.githubAuth = res.data;
          const {access_token, token_type} = res.data;
          // 請求獲得用戶信息
          const userInfoRes = await axios({
            method: 'GET',
            url: github.user_info_url,
            headers: {
              Authorization: `token ${access_token}`
            }
          });
          // 存儲用戶信息到session和redis
          ctx.session.userInfo = userInfoRes.data;
          // 受權等流程結束以後,重定向到首頁
          ctx.redirect('/');
        } else {
          const errorMsg = res.data && res.data.error;
          ctx.body = `request token failed ${errorMsg}`
        }
      } else {
        ctx.body = 'code not exist';
      }
    } else {
      await next()
    }
  })
};

// server.js
// 對服務端的session進行加密
server.use(session(SESSION_CONFIG, server));
// 配置處理github oauth登錄
auth(server);
複製代碼

項目地址

相關文章
相關標籤/搜索