OAuth是一種授權機制。OAuth過程中,系統會詢問資料所有者,是否同意授權第三方應用進入系統獲取這些資料,同意,則系統將產生一個短期的進入令牌(token),用來代替密碼,供第三方應用使用。
# OAuth流程(假設你的站點是A網站)
1. 使用者事件觸發(一般點選事件)跳轉,到 Github
2. Github 要求使用者登入,並詢問使用者是同意 Github 下放授權碼給 A 網站
3. 使用者同意,則 Github 重定向到 A 網站,同時攜帶一個授權碼(code,以url拼接形式下放)
4. A 網站使用授權碼(code)向 Github 請求令牌(token)
5. Github 返回令牌(token)
6. A 網站使用令牌,向 Github 請求使用者資料
7. A 網站取得使用者 Github 資料,前端展示登陸,後端儲存更新使用者資料,並記錄登入狀態
複製程式碼
本示例中,專案前後端分離開發,前端採用大家熟知的vue,後端採用koa2,OAuth邏輯和前後端框架其實無關,只會因為前後端分離在寫法和前後端不分離專案上略有不同。下面分別來說說進行OAuth開發前做的準備,以及在前後端專案中如何編寫程式碼。
一、配置Github
Application name:站點名稱,這裡假設是a
Homepage URL: 主站連結,本地測試,這裡是localhost:8001
Authorization callback URL:Github重定向連結,本地測試,這裡是localhost:8001/login
確認後,github將生成OAuth所需要的 clientID 和 clientSecret
二、前端編寫
// 將登入方法封裝為外掛 oauth.js
const getQuery = require('./getQuery') // 個人編寫用來獲取url query的方法
const oauthGithub = {
getCode() {
const authorize_uri = 'https://github.com/login/oauth/authorize';
const client_id = 'b82f7274e2e996a2cecc';
const redirect_uri = 'http://localhost:8001/login';
location.href = `${authorize_uri}?client_id=${client_id}&redirect_uri=${redirect_uri}`;
},
async getUser(_this) {
const code = getQuery('code');
if (code) {
try {
const res = await _this.$axios.post(`/api/oauth/github?code=${code}`);
if (res.data.errorno) {
_this.$root.$emit('toast', { variant: 'danger', text: '登入失敗' }); // 自定義toast
} else {
_this.$store.commit('setUser', res.data.data); // 儲存使用者資料
_this.$root.$emit('toast', { variant: 'success', text: '登入成功' });
}
} catch(err) {
_this.$root.$emit('toast', { variant: 'danger', text: '登入失敗' });
}
}
}
}
module.exports = {
oauthGithub
}
複製程式碼
// 在頁面或元件內呼叫外掛方法
<template>
<b-card>
<b-row>
<b-col md="4" class="border-right">
<h5 class="pb-2 text-center">第三方登入</h5>
<div class="flex justify-center">
<div class="pointer hover" @click="getCode()">
<b-icon icon="github" font-scale="3"></b-icon>
<p class="m-0 p-0">Github</p>
</div>
</div>
</b-col>
</b-row>
</b-card>
</template>
<script>
import { oauthGithub } from '~/plugins/oauth'
export default {
name: 'login',
data() {
return {}
},
mounted() {
oauthGithub.getUser(this); // Github下放code重定向後呼叫
},
methods: {
getCode() {
oauthGithub.getCode(); // 點選事件觸發,獲取code
}
}
}
</script>
複製程式碼
二、後端編寫
// 將OAuth單獨抽離為一個route:oauth.js
const router = require('koa-router')()
const axios = require('axios')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const {
handleUser
} = require('../controller/oauth')
router.prefix('/api/oauth')
router.post('/github', async function (ctx, next) {
const clientID = 'b82f7274e2e996a2cecc'
const clientSecret = 'f09aa2=dfhf4ba57b6777307b6200d6d2a'
const code = ctx.query.code
const tokenRes = await axios({
method: 'post',
url: `https://github.com/login/oauth/access_token?client_id=${clientID}&client_secret=${clientSecret}&code=${code}`,
headers: { accept: 'application/json' }
})
const accessToken = tokenRes.data.access_token
if (accessToken) {
const resData = await axios({
method: 'get',
url: `https://api.github.com/user`,
headers: { accept: 'application/json', Authorization: `token ${accessToken}` }
});
const uname = resData.data.login // 使用者github賬號名
const reqBody = {
nickname: resData.data.name, // 使用者github暱稱
avatar: resData.data.avatar_url, // 使用者github頭像
self_introduction: resData.data.bio, // 使用者github自我介紹
site: 'github', // 標識使用者來自github
site_id: resData.data.id, // 使用者github id
create_at: Date.now(), // 使用者首次 OAuth 時間
update_at: Date.now() // 使用者最近一次 OAuth 時間
}
const userData = await handleUser(reqBody, uname) // controller方法,使用者首次登入則儲存使用者,二次及以後登入更新資料
if (userData.id) {
ctx.session.uid = userData.id //redis儲存登入狀態
ctx.body = new SuccessModel(userData)
} else {
ctx.body = new ErrorModel('登入失敗')
}
}
})
module.exports = router
複製程式碼
三、操作演示
- A 網站跳轉到 Github
- 使用者確認後,GIthub 重定向到 A 網站,並攜帶 code
- 前端拿到URL query 部分的 code,向 Github 請求資料(token及使用者資料獲取都在都在後端完成)
// 即route: oauth.js 部分邏輯
// 1. 拿到前端傳過來的code,向github請求token
// 2. 拿到token,向github請求使用者資料
// 3. 拿到使用者資料,將資料返回給前端
複製程式碼
- 前端拿到使用者資料,進行展示