本文最后更新于:2024年4月25日 上午
设计思路
新建OAuth Apps
打开GitHub
->setting
->Developer settings
->OAuth Apps
点New OAuth App
新建一个应用
博客后台
路由表
router
目录下的modules
新建login.ts
1 2 3 4 5 6 7 8 9 10
| export const Login = [{ path: '/login', name: 'login', component: () => import('@/views/login/index.vue'), }, { path: '/login/code', component: () => import('@/views/login/callback/index.vue') }]
|
router/index.ts
导入login.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createRouter, createWebHistory } from 'vue-router' import { Login } from './modules/login'
const routes = [ { path: '/', redirect: '/login' }, { path: '/home',
}, ...Login ]
export const router = createRouter({ history: createWebHistory(), routes })
|
登录页
iconfont图标库
注册iconfont
选择图标添加到项目,打开我的项目,选择Symbol
打开链接,复制Javascript
代码
assets
新建fonts
目录,新建fonts/index.js
把上一步链接代码复制到index.js
main.ts
导入图标js
app.vue
设置全局class="icon"
css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <router-view></router-view> </template>
<style lang="scss"> body { margin: 0; }
.icon { vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style>
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-GitHub"></use> </svg> </template>
<style lang="scss" scoped> .icon { width: 2rem; height: 2rem; } </style>
|
登录页面
views
目录下新建login
目录,新建index.vue
作为登录页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <template> <div class="container"> <el-card class="box-card"> <template #header> <div class="card-header"> <span>管理员认证</span> <el-button class="button" type="text">使用说明</el-button> </div> </template> <div class="authorization"> <a href="https://github.com/login/oauth/authorize?client_id=db7547efa91798d7e956" title="GitHub授权认证"> <i class="iconfont icon-GitHub"></i> </a> </div> <el-divider>第三方授权认证</el-divider> </el-card> </div> </template>
<style lang="scss" scoped> .container { width: 100%; position: fixed; top: 0; bottom: 0; display: flex; justify-content: center; align-items: center; }
.authorization { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%;
a { color: #000; text-decoration: none; }
a > i { font-size: 40px; } }
.card-header { display: flex; justify-content: space-between; align-items: center; }
.text { font-size: 14px; }
.item { margin-bottom: 18px; }
.box-card { width: 480px; } </style>
|
回调中转页
login
文件夹下新建callback
文件夹,新建index.vue
作为回调中转页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <script lang="ts" setup> import { router } from '@/router' import { ApiGet } from '@/utils/request' import { ElMessage, ElLoading } from 'element-plus' import { onMounted } from 'vue';
onMounted(async () => { const loading = ElLoading.service({ lock: true, text: '正在人海中搜寻...', background: '#fff', }) const params = new URLSearchParams(location.search) const code = params.get('code') const res = (await ApiGet('/login/token', { code })).data if (res.statusCode === 2000) { ElMessage.success('欢迎回家φ(゜▽゜*)♪') loading.close() router.push({ path: '/home' }) } else { setTimeout(() => { ElMessage.error('授权失败ψ(`∇´)ψ') loading.close() router.push({ path: '/login' }) }, 2000) } }) </script>
|
后端
配置环境变量
用.env
文件存储client_id
与client_secret
根目录新建.env
文件
1 2 3
| CLIENT_ID: "上一步的client_id" CLIENT_SECRET: "上一步的client_secret" USER_ID: 666666<管理员的GitHubID>
|
.gitignore
添加.env
,这样上传到仓库就看不到密匙了
使用环境变量
1 2 3
| import dotenv from 'dotenv'
const config = dotenv.config().parsed
|
安装axios
第三方授权需要请求信息,所以使用axios
请求
src
目录下新建utils
文件夹,新建request.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import axios from 'axios'
const ApiGet = (url: string, params: any, config?: any) => { return axios.get(url, { ...params, ...config }) }
const ApiPost = (url: string, data: any, config?: any) => { return axios.post(url, data, config) }
export { ApiPost, ApiGet }
|
提供授权
获取token
api
文件夹下新建types
文件夹,新建index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| interface Params { client_id: string, client_secret: string, code: string }
interface Message { login: string, id: number, token: string, avatar_url: string }
interface ResponseData { statusCode: number, message: string, data: Message | null }
export { Params, ResponseData }
|
login
文件夹下新建getGithubToken.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import { Request, Response } from 'express' import { ApiPost, ApiGet } from '../../utils/request' import { Params, ResponseData } from './types' import dotenv from 'dotenv'
const config = dotenv.config().parsed
const params: Params = { client_id: config!.CLIENT_ID, client_secret: config!.CLIENT_SECRET, code: '', }
const responseData: ResponseData = { statusCode: 2000, message: 'success', data: null, }
const setResponseData = (newData: ResponseData) => { responseData.statusCode = newData.statusCode responseData.message = newData.message responseData.data = newData.data }
export const getGithubToken = { GitToken: async (req: Request, res: Response) => { params.code = String(req.query.code)
let token = '' try { const data = await ApiPost('https://github.com/login/oauth/access_token', params) token = data.data.split('&')[0].split('=')[1] } catch (e: any) { setResponseData({ statusCode: 5001, message: '获取GitHub token失败', data: null }) }
try { const userData = await ApiGet('https://api.github.com/user', { headers: { 'Authorization': 'token ' + token } })
if (userData.status === 200 && userData.data.login) { if (userData.data.id != config!.USER_ID) setResponseData({ statusCode: 4003, message: '用户信息不匹配', data: null }) else setResponseData({ statusCode: 2000, message: 'success', data: { login: userData.data.login, id: userData.data.id, token: token, avatar_url: userData.data.avatar_url, } }) } else setResponseData({ statusCode: 4001, message: '获取用户信息失败', data: null }) } catch (e: any) { setResponseData({ statusCode: 5002, message: '无效token', data: null }) }
res.send(responseData) } }
|
router
目录下index.ts
注册/login/token
路径
1 2 3 4 5 6 7 8 9
| import express, { Router } from 'express' import { getGithubToken } from '../api/login/getGithubToken'
const router: Router = express.Router()
router.get('/login/token', getGithubToken.GitToken)
export default router
|
最终效果
登录页
授权页
中转页
跳回app首页