vue基于session和github-oauth2實(shí)現(xiàn)登錄注冊(cè)驗(yàn)證思路詳解
本文簡(jiǎn)介
本文講解前端通過(guò)簡(jiǎn)單的方式實(shí)現(xiàn)一個(gè)基本的登錄注冊(cè)驗(yàn)證功能。在后端,使用 session 表來(lái)存儲(chǔ)會(huì)話信息,并讓前端的 localStorage 存儲(chǔ) sessionId。通過(guò) sessionId 可以在 session 表中獲取用戶的信息。此外,還利用 session 表實(shí)現(xiàn)了 GitHub 的 OAuth2 第三方登錄。
基本思路
在本項(xiàng)目中,我們使用 Vue 作為前端框架,通過(guò)與后端 API 交互來(lái)管理用戶的會(huì)話狀態(tài)。
- 用戶注冊(cè)和登錄:用戶在前端輸入用戶名和密碼,通過(guò)表單提交到后端進(jìn)行驗(yàn)證。成功登錄后,后端返回一個(gè)
sessionId,前端將其存儲(chǔ)在localStorage中。 - 會(huì)話管理:前端在每次請(qǐng)求時(shí)會(huì)在請(qǐng)求頭中附帶
sessionId,以便后端能夠驗(yàn)證用戶的身份。 - 第三方登錄:用戶點(diǎn)擊
GitHub登錄按鈕后,前端會(huì)重定向到GitHub的授權(quán)頁(yè)面。用戶授權(quán)后,GitHub重定向回我們的網(wǎng)站,并附帶授權(quán)碼。前端將授權(quán)碼發(fā)送到后端服務(wù)器以換取訪問(wèn)令牌。
框架版本
- Vue 3
- Pinia (用于狀態(tài)管理)
- Vue Router (用于路由管理)
- Axios (用于 HTTP 請(qǐng)求)
- tailwind (用于css)
- element-plus (用于css)
開(kāi)源地址
后端地址:springboot-session-github-oauth
前端地址:vite-session-github-oauth
主要學(xué)習(xí)點(diǎn)
路由守衛(wèi)
路由守衛(wèi)確保只有經(jīng)過(guò)身份驗(yàn)證的用戶才能訪問(wèn)某些頁(yè)面。
// 添加全局前置守衛(wèi)
router.beforeEach(async (to, from, next) => {
if (to.name == "Home") {
const sessionId = ref("");
const sessionIdFromUrl = to.query.sessionId as string;
if (sessionIdFromUrl) {
sessionId.value = sessionIdFromUrl;
localStorage.setItem('sessionId', sessionIdFromUrl);
} else if (localStorage.getItem('sessionId')) {
sessionId.value = localStorage.getItem('sessionId') || '';
}
if (sessionId.value == '') {
// 沒(méi)有 sessionId,重定向到登錄頁(yè)面
return next({ name: 'Login' });
} else {
try {
// 驗(yàn)證 sessionId 是否有效
const response = await axios.get('http://localhost:8080/auth/home', {
params: { sessionId: sessionId.value }
});
if (response.data && response.data.data) {
// 如果 username 存在,則 sessionId 有效
localStorage.setItem('username', response.data.data);
next();
} else {
// 如果無(wú)效,清除 localStorage 并重定向到登錄頁(yè)面
localStorage.removeItem('sessionId');
next({ name: 'Login' });
}
} catch (error) {
console.error('驗(yàn)證 sessionId 失敗:', error);
localStorage.removeItem('sessionId');
next({ name: 'Login' });
}
}
} else {
// 如果目標(biāo)路由是登錄頁(yè)面,直接繼續(xù)導(dǎo)航
next();
}
});代碼設(shè)計(jì)
Login.vue
<script setup lang="ts">
import { ref } from 'vue';
import axios from 'axios';
import router from '../routes.ts';
const username = ref('');
const password = ref('');
const handleLogin = async () => {
try {
const response = await axios.post('http://localhost:8080/auth/login',{
username: username.value,
password: password.value
});
localStorage.setItem('sessionId', response.data.data);
console.log(response.data.data);
await router.push('/home');
} catch (error) {
console.log(error);
}
}
const handleLoginByGithub = () => {
window.location.href = 'http://localhost:8080/oauth/authorize';
}
</script>
<template>
<div class="flex justify-center items-center min-h-screen bg-gray-2">
<div class="w-full max-w-md p-8 space-y-8 bg-white rounded-lg shadow-lg">
<h2 class="text-2xl font-bold text-center text-gray-800">
客戶端登錄(session登錄)
</h2>
<form class="space-y-6" @submit.prevent>
<div>
<label for="username" class="block text-sm font-medium text-gray-700">
用戶名
</label>
<input v-model="username" id="username" name="username" type="text" required
class="w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" />
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">
密碼
</label>
<input v-model="password" id="password" name="password" type="password" required
class="w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" />
</div>
<div class="flex justify-end">
<router-link to="/register">
還沒(méi)有賬號(hào),點(diǎn)擊注冊(cè)
</router-link>
</div>
<div>
<button type="submit"
class="w-full px-4 py-2 font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="handleLoginByGithub">
使用GiHub登錄
</button>
</div>
<div>
<button type="submit"
class="w-full px-4 py-2 font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="handleLogin">
登錄
</button>
</div>
</form>
</div>
</div>
</template>
<style scoped></style>Register.vue
<script setup lang="ts">
import { ref } from 'vue';
import axios from 'axios';
import router from '../routes';
const username = ref('');
const password = ref('');
const handleRegister = async () => {
try {
const response = await axios.post('http://localhost:8080/auth/register', {
username: username.value,
password: password.value,
});
console.log(response.data);
localStorage.setItem('sessionId', response.data.data);
await router.push('/home');
} catch (error) {
console.log(error);
}
};
</script>
<template>
<div class="flex justify-center items-center min-h-screen bg-gray-100">
<div class="w-full max-w-md p-8 space-y-8 bg-white rounded-lg shadow-lg">
<h2 class="text-2xl font-bold text-center text-gray-800">
客戶端注冊(cè)(session注冊(cè))
</h2>
<form class="space-y-6" @submit.prevent="handleRegister">
<div>
<label for="username" class="block text-sm font-medium text-gray-700">
用戶名
</label>
<input v-model="username" id="username" name="username" type="text" required
class="w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" />
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">
密碼
</label>
<input v-model="password" id="password" name="password" type="password" required
class="w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" />
</div>
<div class="flex justify-end">
<router-link to="/login">
已有賬號(hào)?點(diǎn)擊登錄
</router-link>
</div>
<div>
<button type="submit"
class="w-full px-4 py-2 font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click.prevent="handleRegister">
注冊(cè)
</button>
</div>
</form>
</div>
</div>
</template>
<style scoped></style>Home.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import axios from 'axios';
import router from '../routes';
import { ElMessage } from 'element-plus';
const username = ref('');
const sessionId = ref('');
const state = ref('');
const showPasswordDialog = ref(false);
const newPassword = ref('');
const confirmPassword = ref(''); // New ref for confirming password
username.value = localStorage.getItem('username');
sessionId.value = localStorage.getItem('sessionId');
console.log(sessionId.value);
const handleState = async () => {
try {
const response = await axios.get('http://localhost:8080/user/state', {
params: {
sessionId: sessionId.value
}
});
state.value = response.data.data;
} catch (error) {
console.log(error);
}
};
const handleAddPassword = async () => {
if (newPassword.value !== confirmPassword.value) {
ElMessage.error('Passwords do not match');
return;
}
try {
const response = await axios.post('http://localhost:8080/user/addPassword', {
password: newPassword.value
}, {
params: {
sessionId: sessionId.value,
}
});
console.log(response.data);
if (response.data.status === 'OK') {
ElMessage.success('Password added successfully');
showPasswordDialog.value = false;
newPassword.value = '';
confirmPassword.value = ''; // Reset password fields
} else {
ElMessage.error('Failed to add password');
}
} catch (error) {
console.log(error);
ElMessage.error('An error occurred while adding the password');
}
};
const handleLoginByGitHub = async () => {
window.location.href = 'http://localhost:8082/oauth/authorize?sessionId=' + sessionId.value;
}
const handleLogout = async () => {
try {
const response = await axios.get('http://localhost:8080/auth/logout', {
params: {
sessionId: sessionId.value
}
});
localStorage.removeItem('sessionId');
sessionId.value = '';
await router.push('/login');
console.log(response.data);
} catch (error) {
console.log(error);
}
};
// Execute on component mount
onMounted(() => {
handleState();
});
</script>
<template>
<div class="flex justify-center items-center min-h-screen bg-gray-2">
<div class="w-full max-w-md p-8 space-y-8 bg-white rounded-lg shadow-lg">
<h2 class="text-2xl font-bold text-center text-gray-800">
使用Session登錄成功
</h2>
<p class="text-center text-gray-600">
歡迎回來(lái), {{ username }}
</p>
<div v-if="state == 'password'" class="flex justify-end">
<button
class="px-4 py-2 font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="showPasswordDialog = true">
添加密碼
</button>
</div>
<div v-else-if="state == 'githubLoginName'" class="flex justify-end">
<button
class="px-4 py-2 font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="handleLoginByGitHub">
綁定github賬號(hào)
</button>
</div>
<div class="flex justify-end">
<button
class="px-4 py-2 font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="handleLogout">
退出
</button>
</div>
</div>
</div>
<!-- Password Dialog -->
<el-dialog v-model="showPasswordDialog" title="添加密碼" width="500" draggable>
<div class="space-y-4">
<!-- New Password -->
<div class="flex items-center space-x-4">
<label for="newPassword" class="text-sm font-medium text-gray-700">
輸入密碼
</label>
<input v-model="newPassword" id="newPassword" name="newPassword" type="password" required
class="w-5/6 px-4 py-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" />
</div>
<!-- Confirm Password -->
<div class="flex items-center space-x-4">
<label for="confirmPassword" class="text-sm font-medium text-gray-700">
確認(rèn)密碼
</label>
<input v-model="confirmPassword" id="confirmPassword" name="confirmPassword" type="password" required
class="w-5/6 px-4 py-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" />
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="showPasswordDialog = false">取消</el-button>
<el-button type="primary" @click="handleAddPassword">確定</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped></style>到此這篇關(guān)于vue基于session和github-oauth2實(shí)現(xiàn)登錄注冊(cè)驗(yàn)證的文章就介紹到這了,更多相關(guān)vue登錄注冊(cè)驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue-cli如何關(guān)閉Uncaught?error的全屏提示
這篇文章主要介紹了vue-cli如何關(guān)閉Uncaught?error的全屏提示問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
Vue實(shí)現(xiàn)將數(shù)據(jù)庫(kù)中帶html標(biāo)簽的內(nèi)容輸出(原始HTML(Raw HTML))
今天小編就為大家分享一篇Vue實(shí)現(xiàn)將數(shù)據(jù)庫(kù)中帶html標(biāo)簽的內(nèi)容輸出(原始HTML(Raw HTML)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10
vue不通過(guò)路由直接獲取url中參數(shù)的方法示例
通過(guò)url傳遞參數(shù)是我們?cè)陂_(kāi)發(fā)中經(jīng)常用到的一種傳參方法,但通過(guò)url傳遞后改如果獲取呢?下面這篇文章主要給大家介紹了關(guān)于vue如何不通過(guò)路由直接獲取url中參數(shù)的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08
使用Vue3優(yōu)雅地實(shí)現(xiàn)表格拖動(dòng)排序
在?Vue.js?中主要通過(guò)第三方庫(kù)實(shí)現(xiàn)表格拖動(dòng)排序功能,其中最常用的庫(kù)是?SortableJS,下面我們就來(lái)看看如何使用SortableJS實(shí)現(xiàn)表格拖動(dòng)排序吧2025-01-01

