前言
第三方登入太常見(jiàn)了徽千,微信,微博珍手,QQ...總有一個(gè)你用過(guò)办铡。
在開發(fā)中,我們希望用戶可以通過(guò)GitHub賬號(hào)登錄我們的網(wǎng)站琳要,這樣用戶就不需要注冊(cè)賬號(hào)寡具,直接通過(guò)GitHub賬號(hào)登錄即可。
效果演示
注冊(cè)配置 GitHub 應(yīng)用
1.首先登錄你的GitHub然后點(diǎn)擊右上角的頭像->點(diǎn)擊進(jìn)入Settings
頁(yè)面
2.在Settings
頁(yè)面中點(diǎn)擊左側(cè)邊欄的 Developer settings
3.然后點(diǎn)擊OAuth Apps
稚补,點(diǎn)擊 Register a new application
一個(gè)用戶或組織最多可以擁有100個(gè)OAuth應(yīng)用童叠。
4.填寫應(yīng)用信息
我這邊使用了騰訊翻譯插件,為了照顧英語(yǔ)不好的朋友觀看理解。
這里主要是Authorization callback URL
的填寫厦坛;
這個(gè)應(yīng)用回調(diào)地址就是上面登錄流程授權(quán)之后返回的redirect_uri
五垮。
5.點(diǎn)擊Generate new client secret
生成Client Secret
6.將Client ID
和Client Secret
復(fù)制到配置文件,用于后面向GitHub發(fā)送請(qǐng)求傳參杜秸。
注意: 只會(huì)出現(xiàn)一次Client secrets
,自己保存好
前后端調(diào)用流程步驟:
- 前端:用戶點(diǎn)擊按鈕跳轉(zhuǎn)到GitHub授權(quán)頁(yè)面放仗;
- 前端:用戶在授權(quán)頁(yè)面同意授權(quán)后,GitHub將用戶重定向到您的網(wǎng)站撬碟;
- 前端:重定向的URL中包含一個(gè)授權(quán)碼code诞挨,在該頁(yè)面中獲取授權(quán)碼;
- 前端:調(diào)用后端登錄api呢蛤,將獲取到的code傳給后端惶傻;
- 后端:后端收到code后調(diào)用GitHub的token api,獲取access_token其障;
- 后端:獲取到access_token后調(diào)用 user api银室,獲取用戶信息返回給前端;
- 前端:拿到后端返回的用戶信息后静秆,將用戶信息保存到本地粮揉,完成登錄。
前端 Vue 實(shí)現(xiàn)
1.安裝必要依賴
- axios是一個(gè)HTTP客戶端庫(kù)抚笔,用于向服務(wù)端發(fā)送請(qǐng)求扶认。
npm install axios
2.替換你的配置信息
// github配置信息
const config = {
// 替換為你的回調(diào)地址
redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
// 替換為你的 client_id
client_id: 'Ov23li3ZcThmL87YHUBL',
}
3.代碼示例
<template>
<div class="user-box">
<div v-if="userInfo.id" class="user-info">
<img class="user-img" :src="userInfo.avatar_url" />
<text class="user-name">用戶昵稱:{{ userInfo.name || userInfo.login }}</text>
<text class="user-openid">nodeId{{ userInfo.node_id }}</text>
</div>
<div v-else class="user-empty">
{{ loading ? '用戶登錄中...' : userInfo?.id ? '用戶已登錄' : '用戶未登錄' }}
</div>
</div>
<button @click="oauth">發(fā)起GitHub授權(quán)</button>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import axios from 'axios'
const loading = ref(false)
let userInfo = ref({})
// github配置信息
const config = {
// 替換為你的回調(diào)地址
redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
// 替換為你的 client_id
client_id: 'Ov23li3ZcThmL87YHUBL',
}
// 請(qǐng)求后端登錄
function reqLogin(code) {
console.log('3.將獲取到的code發(fā)送給后端進(jìn)行登錄');
if (loading.value) return
loading.value = true
axios.post('http://127.0.0.1:3000/api/github/login', { code })
.then(res => {
// 前端拿到用戶信息后,可以保存到數(shù)據(jù)庫(kù)或者本地殊橙,或者直接跳轉(zhuǎn)到個(gè)人中心頁(yè)面辐宾。
console.log('4.登錄成功獲取到用戶信息', res.data);
userInfo.value = res.data
}).catch(err => {
console.log('登錄出錯(cuò)了!', err);
}).finally(() => {
console.log('finally');
loading.value = false
})
}
// 獲取地址欄中的code 獲取不到將返回 null
function getCode() {
// 獲取當(dāng)前 URL 的查詢參數(shù)
const urlParams = new URLSearchParams(window.location.search);
// 從查詢參數(shù)中獲取 'code' 值
const code = urlParams.get('code');
if (code) {
console.log('2.授權(quán)后膨蛮,獲取地址欄中的code');
}
return code
}
// 發(fā)起授權(quán)
function oauth() {
console.log('1.點(diǎn)擊授權(quán)按鈕叠纹,跳轉(zhuǎn)到GitHub授權(quán)頁(yè)中');
const url = `https://github.com/login/oauth/authorize?client_id=${config.client_id}&redirect_uri=${config.redirect_uri}`
window.location.href = url
}
onMounted(() => {
const code = getCode()
if (code) reqLogin(code)
})
</script>
<style scoped lang="scss">
.user-box {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
.user-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.user-img {
width: 60px;
height: 60px;
border-radius: 99px;
border: 1px solid black;
}
}
.user-empty {
// background-color: #f5f6f7;
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 100px;
border: 1px solid black;
border-radius: 6px;
}
}
</style>
后端 Node.js 實(shí)現(xiàn)
1.安裝必要依賴
- express是一個(gè)Web應(yīng)用框架,用于構(gòu)建Web應(yīng)用敞葛。
- cors是一個(gè)中間件誉察,用于處理跨域請(qǐng)求。
- axios是一個(gè)HTTP客戶端庫(kù)惹谐,用于向服務(wù)端發(fā)送請(qǐng)求持偏。
npm install express cors axios
2.替換你的配置信息
// github配置信息
const githubConfig = {
// 替換為你的回調(diào)地址
redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
// 替換為你的 client_id
client_id: 'Ov23li3ZcThmL87YHUBL',
// 替換為你的 client_secret
client_secret: 'e021cb59a650476e62be3ee72fc9686e2c86c1d3',
}
3.代碼示例
// Node.js和vue3實(shí)現(xiàn)GitHub OAuth第三方登錄
const express = require('express'); // 導(dǎo)入 Express 模塊
const cors = require('cors'); // 導(dǎo)入 CORS 模塊,用于處理跨域請(qǐng)求
const axios = require('axios'); // 導(dǎo)入 Axios 模塊氨肌,用于發(fā)起 HTTP 請(qǐng)求
const app = express(); // 創(chuàng)建 Express 應(yīng)用實(shí)例
app.use(cors()); // 使用 CORS 中間件解決跨越請(qǐng)求
app.use(express.json()) // 解析 json 格式請(qǐng)求體
app.use(express.urlencoded({ extended: true })) // 解析傳統(tǒng)表單請(qǐng)求體
// github配置信息
const githubConfig = {
// 替換為你的回調(diào)地址
redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
// 替換為你的 client_id
client_id: 'Ov23li3ZcThmL87YHUBL',
// 替換為你的 client_secret
client_secret: 'e021cb59a650476e62be3ee72fc9686e2c86c1d3',
}
// github登錄
app.post('/api/github/login', async (req, res) => {
// 1鸿秆、校驗(yàn)必填參數(shù)
if (!req.body.code) {
throw new Error('必填參數(shù)不能為空!')
}
// 2怎囚、獲取 Access token
const accessTokenInfo = await getAccessToken(req.body.code)
// 3卿叽、獲取用戶信息
const userInfo = await getUserInfo(accessTokenInfo.access_token)
// 4、在這步你可以將用戶信息存入數(shù)據(jù)庫(kù)中等其他操作,這里我直接返回了
res.status(200).send(userInfo)
})
// 獲取 access_token
async function getAccessToken(code) {
//官方文檔: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
// 1考婴、驗(yàn)證后端傳來(lái)的code
if (!code || code.length !== 20) {
throw new Error('code參數(shù)不正確贩虾!');
}
// 2、向github發(fā)送post請(qǐng)求蕉扮,成功的話會(huì)整胃,response.data里面有一個(gè)access_token
const response = await axios({
method: 'post',
url: 'https://github.com/login/oauth/access_token',
params: {
redirect_uri: githubConfig.redirect_uri,
client_id: githubConfig.client_id,
client_secret: githubConfig.client_secret,
code,
},
headers: { 'accept': 'application/json' },
});
if (!response.data?.access_token) {
throw new Error('獲取 access_token 失敗喳钟!' + JSON.stringify(response.data))
}
// response.data:{
// "access_token":"gho_16C7e42F292c6912E7710c838347Ae178B4a",
// "scope":"repo,gist",
// "token_type":"bearer"
// }
return response.data
}
// 獲取用戶信息
async function getUserInfo(access_token) {
//官方文檔: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#3-use-the-access-token-to-access-the-api
const response = await axios({
method: "get",
url: 'https://api.github.com/user',
headers: {
Authorization: `Bearer ${access_token}`,
},
});
if (!response.data?.id) throw new Error('獲取用戶信息失敗在岂!')
/**
response.data = {
"login": "China-quanda",
"id": 36378336,
"node_id": "MDQ6VXNlcjM2Mzc4MzM2",
"avatar_url": "https://avatars.githubusercontent.com/u/36378336?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/China-quanda",
"html_url": "https://github.com/China-quanda",
"followers_url": "https://api.github.com/users/China-quanda/followers",
"following_url": "https://api.github.com/users/China-quanda/following{/other_user}",
"gists_url": "https://api.github.com/users/China-quanda/gists{/gist_id}",
"starred_url": "https://api.github.com/users/China-quanda/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/China-quanda/subscriptions",
"organizations_url": "https://api.github.com/users/China-quanda/orgs",
"repos_url": "https://api.github.com/users/China-quanda/repos",
"events_url": "https://api.github.com/users/China-quanda/events{/privacy}",
"received_events_url": "https://api.github.com/users/China-quanda/received_events",
"type": "User",
"site_admin": false,
"name": "Quanda",
"company": null,
"blog": "",
"location": "北京",
"email": null,
"hireable": null,
"bio": null,
"twitter_username": null,
"notification_email": null,
"public_repos": 6,
"public_gists": 0,
"followers": 0,
"following": 2,
"created_at": "2018-02-11T17:35:16Z",
"updated_at": "2024-09-07T10:31:22Z"
}
*/
return response.data;
}
// 啟動(dòng)服務(wù)
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://127.0.0.1:${PORT}`);
});
總結(jié)
- 首先奔则,在github上注冊(cè)一個(gè)應(yīng)用,并配置好回調(diào)地址蔽午,獲取
client_id
和client_secret
易茬。 - 在前端頁(yè)面上,通過(guò)點(diǎn)擊
發(fā)起GitHub授權(quán)
按鈕及老,替換當(dāng)前地址為https://github.com/login/oauth/authorize
抽莱,攜帶上我們前面獲取的client_id
和回調(diào)地址。 - github會(huì)返回一個(gè)
code
骄恶,這個(gè)code
是臨時(shí)的食铐,我們通過(guò)這個(gè)code
向github請(qǐng)求access_token
,再通過(guò)access_token
向github請(qǐng)求用戶信息僧鲁。 - 最后虐呻,將用戶信息返回給前端,前端拿到用戶信息后寞秃,可以保存到數(shù)據(jù)庫(kù)或者本地斟叼,或者直接跳轉(zhuǎn)到個(gè)人中心頁(yè)面。
- 注意春寿,這個(gè)項(xiàng)目只是演示如何實(shí)現(xiàn)github登錄朗涩,實(shí)際應(yīng)用中,需要做更多的處理绑改,比如用戶注冊(cè)谢床,用戶信息保存等。
- 示例代碼僅供參考绢淀,實(shí)際應(yīng)用中萤悴,需要根據(jù)具體的業(yè)務(wù)需求進(jìn)行修改。
- 示例代碼中皆的,沒(méi)有做任何的錯(cuò)誤處理覆履,實(shí)際應(yīng)用中,需要做錯(cuò)誤處理。
我們發(fā)現(xiàn)第三方登錄的流程其實(shí)都差不多硝全,差別就是不同的平臺(tái)栖雾,和自己應(yīng)用的業(yè)務(wù)會(huì)有點(diǎn)不一樣。所以呢伟众,在做之前先要理清思路析藕,仔細(xì)看文檔。