Vue3.5 + Node.js + Express 实现完整登录注册鉴权流程

news/2025/9/26 16:08:10/文章来源:https://www.cnblogs.com/xikang/p/19113693

在前后端项目里,登录注册和权限鉴权基本算是 “老大难” 问题。做得不严谨,系统就会有漏洞;做得太复杂,又会让开发效率大打折扣。

这篇文章,我结合 Vue3.5 和 Node.js + Express,带大家梳理一下完整的登录注册鉴权流程。

​​​​

技术栈选择
先交代一下这次用到的工具和框架:

前端:Vue 3.5(Composition API 写法)、Vue Router 4(路由跳转)、Pinia(全局状态)、Axios(发请求必备)。
后端:Node.js + Express(轻量级 Web 框架),JWT(JSON Web Token,用来生成和验证令牌)。
数据库层:Prisma(ORM 工具,类型安全,开发体验好),数据库我用 SQLite 做示例,换成 MySQL 也很顺滑。
安全相关:bcrypt(密码加密),cookie-parser(处理 Cookie),cors(跨域问题解决)。
可以看到,这套组合既保证了开发效率,又覆盖了常见的安全环节。

一、整体思路
简单说,鉴权系统就是解决一个核心问题:“如何证明用户身份?”。

所以整套流程其实可以分为三步:

  1. 信任建立(注册 / 登录)
    注册:用户提交信息 → 后端校验 → 用 bcrypt 加密密码 → 存进数据库。
    登录:用户输入账号密码 → 后端验证 → 如果正确,就签发一个 JWT。
  2. 信任验证(接口访问)
    用户之后的每一次请求,都要带上这个 JWT。后端拿到之后做几件事:

验证令牌有没有过期
判断用户是否有访问该资源的权限
通过了,才放行。

  1. 信任终止(登出 / 过期)
    主动登出:前端清除 Cookie 或 localStorage 里的令牌,强制重新登录。
    被动过期:JWT 到期,后端拒绝请求,前端再引导用户跳登录页。
    为什么这样设计?
    几个关键点值得一提:

安全性:令牌放在 HttpOnly Cookie 里,前端 JS 拿不到,有效防 XSS;密码永远是加密存储,避免泄露风险。
开发效率:Prisma 的类型安全让我们写数据库操作很舒服,少掉很多 SQL 报错;Vue3.5 的 Composition API 让状态管理和逻辑复用更简洁。
可扩展性:如果以后要做多角色权限管理(RBAC),JWT 里本身就能塞角色字段,非常灵活。
无状态性:JWT 自包含信息,后端不用维护会话,天然支持分布式部署,不怕多台服务器之间 session 不同步的问题。
二、数据模型设计(Prisma 核心)
要让鉴权体系跑起来,第一步当然是数据库建模。这里我用 Prisma 来管理数据结构,主要优势是:类型安全、自动生成客户端操作方法,还能避免写生涩的 SQL。

在我们的场景里,最核心的数据模型就是 用户表。它必须存储用户名、邮箱、加密密码,还有用户角色。

  1. Prisma Schema 定义(prisma/schema.prisma)
    // 数据源配置(开发环境使用SQLite,生产可切换为MySQL/PostgreSQL)

datasource db {

provider = "sqlite"

url      = env("DATABASE_URL")

}

// 生成客户端代码

generator client {

provider = "prisma-client-js"

}

// 用户模型(核心数据结构)

model User {

id        Int      @id @default(autoincrement()) // 自增ID

username  String   @unique // 用户名唯一

email     String   @unique // 邮箱唯一

password  String   // 存储加密后的密码

role      Role     @default(USER) // 角色,默认为普通用户

createdAt DateTime @default(now()) // 创建时间

updatedAt DateTime @updatedAt // 更新时间

}

// 角色枚举(权限控制基础)

enum Role {

USER

ADMIN

}

  1. 生成 Prisma 客户端
    定义模型后,通过命令生成类型安全的客户端代码:

npx prisma migrate dev --name init  # 创建数据库迁移并生成表结构

npx prisma generate                # 生成Prisma客户端

设计思路:

强制username和email唯一,避免重复注册
引入role字段实现基础权限区分,为后续扩展预留空间
自动维护createdAt和updatedAt,便于数据追踪
通过 Prisma 的类型检查,在开发阶段避免数据类型错误
三、信任建立:注册流程
注册是用户与系统建立信任的第一步,需确保数据合法性和密码安全性。

核心原理
用户提交基本信息(用户名、邮箱、密码),经过前后端双重验证后,密码加密存储到数据库。

前端核心代码
// 1. 注册表单逻辑(Vue3.5 Composition API)

import { ref, reactive } from 'vue'

import { useRouter } from 'vue-router'

import api from '@/utils/api'

const register = async (userData) => {

// 基础验证(前端体验优化)

if (!userData.username || userData.username.length < 3) {

throw new Error('用户名至少3个字符')

}

const emailReg = /[\s@]+@[\s@]+.[\s@]+$/

if (!emailReg.test(userData.email)) {

throw new Error('请输入有效邮箱')

}

// withCredentials: true 确保跨域时Cookie能正常传递

const response = await api.post('/auth/register', userData, {

withCredentials: true,

})

return response.data

}

后端核心代码
const { PrismaClient } = require('@prisma/client')

const bcrypt = require('bcrypt')

const prisma = new PrismaClient()

// 注册处理(验证并创建用户)

const register = async (req, res) => {

const { username, email, password } = req.body

// 1. 验证用户名是否已存在

const existingUser = await prisma.user.findUnique({

where: { username },

})

if (existingUser) {

return res.status(409).json({

error: { field: 'username', message: '用户名已被占用' },

})

}

// 2. 验证邮箱是否已注册

const existingEmail = await prisma.user.findUnique({

where: { email },

})

if (existingEmail) {

return res.status(409).json({

error: { field: 'email', message: '邮箱已被注册' },

})

}

// 3. 密码加密(10轮盐值,平衡安全性和性能)

const hashedPassword = await bcrypt.hash(password, 10)

// 4. 创建用户(Prisma类型安全的数据库操作)

const newUser = await prisma.user.create({

data: { username, email, password: hashedPassword },

select: { id: true, username: true, email: true, role: true }, // 只返回非敏感信息

})

res.status(201).json({ message: '注册成功', user: newUser })

}

设计思路详解:

双重验证:前端做基础格式校验(提升用户体验),后端做业务规则校验(确保数据安全)
密码安全:使用 bcrypt 单向加密,即使数据库泄露,密码也无法被还原
错误反馈:返回具体字段的错误信息,帮助用户快速修正
Prisma 优势:通过类型安全的查询方法,避免 SQL 注入风险
四、信任建立:登录流程
登录是验证用户身份并发放信任凭证(JWT)的核心环节,需兼顾安全性和用户体验。

核心原理
将用户提交的 "用户名 + 密码" 转化为服务器可识别的加密令牌(JWT),并通过 HttpOnly Cookie 安全存储。

前端核心代码
// 1. 登录请求(仅负责发送凭证)

const login = async (username, password) => {

// withCredentials: true 是跨域携带Cookie的关键

const response = await axios.post(

'/api/auth/login',

{ username, password },

{ withCredentials: true }

)

// 仅存储用户基本信息,不处理令牌(令牌在Cookie中)

return { user: response.data.user, isLogin: true }

}

// 2. 状态管理(Pinia)

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {

state: () => ({

user: null,

isLogin: false,

}),

actions: {

setUser(userInfo) {

this.user = userInfo

this.isLogin = true

},

clearUser() {

this.user = null

this.isLogin = false

},

},

})

后端核心代码
const jwt = require('jsonwebtoken')

// 1. 登录处理(生成并存储令牌)

const login = async (req, res) => {

const { username, password } = req.body

// 验证用户凭据

const user = await prisma.user.findUnique({

where: { username },

})

// 统一错误信息,避免信息泄露

if (!user || !(await bcrypt.compare(password, user.password))) {

return res.status(401).json({ message: '认证失败,请检查用户名或密码' })

}

// 生成JWT令牌(仅包含必要信息)

const token = jwt.sign(

{ userId: user.id, role: user.role }, // payload

process.env.JWT_SECRET, // 密钥

{ expiresIn: '24h' } // 有效期

)

// 存储令牌到HttpOnly Cookie(核心安全措施)

res.cookie('token', token, {

httpOnly: true, // 禁止JS访问,防XSS

secure: process.env.NODE_ENV === 'production', // 生产环境强制HTTPS

sameSite: 'lax', // 防CSRF

maxAge: 24 * 60 * 60 * 1000, // 24小时有效期

})

// 返回用户信息(不含敏感数据)

res.json({

user: {

id: user.id,

username: user.username,

email: user.email,

role: user.role,

},

})

}

设计思路详解:

令牌内容:JWT 仅包含用户 ID 和角色,不存储密码等敏感信息
安全存储:HttpOnly Cookie 使令牌无法被 JavaScript 访问,从源头防止 XSS 攻击
密码验证:永远不存储明文密码,使用 bcrypt 等算法验证哈希值
错误处理:统一返回 "认证失败",避免泄露用户是否存在的信息
五、信任验证:接口访问流程
所有受保护的请求自动携带 Cookie 中的令牌,服务器通过中间件验证令牌有效性和权限。

核心原理
所有受保护的请求自动携带 Cookie 中的令牌,服务器通过中间件验证令牌有效性和权限。

前端核心代码
// 1. 请求配置(自动携带令牌)

const api = axios.create({

baseURL: '/api',

withCredentials: true, // 自动携带Cookie,无需手动添加Token

})

// 2. 响应拦截器(处理令牌过期)

api.interceptors.response.use(

(response) => response,

(err) => {

if (err.response?.status === 401) {

const userStore = useUserStore()

userStore.clearUser() // 清理前端状态

router.push(/login?redirect=${router.currentRoute.value.fullPath})

}

return Promise.reject(err)

}

)

// 3. 路由守卫(前端权限控制)

router.beforeEach(async (to, from, next) => {

if (!to.meta.requiresAuth) return next()

try {

const { data } = await api.get('/auth/status')

const userStore = useUserStore()

userStore.setUser(data.user)

// 验证角色权限

if (to.meta.requiredRole && data.user.role !== to.meta.requiredRole) {

return next('/403')

}

next()

} catch (err) {

next(/login?redirect=${to.fullPath})

}

})

后端核心代码
// 1. 认证中间件(验证令牌)

const authMiddleware = async (req, res, next) => {

// 从Cookie提取令牌(核心提取逻辑)

const token = req.cookies.token

if (!token) return res.status(401).json({ message: '未登录' })

try {

// 验证令牌有效性

const decoded = jwt.verify(token, process.env.JWT_SECRET)

// 验证用户存在性(防止已注销用户访问)

const user = await prisma.user.findUnique({

where: { id: decoded.userId },

select: { id: true, username: true, role: true, email: true },

})

if (!user) return res.status(401).json({ message: '用户不存在' })

// 将用户信息注入请求对象

req.user = user

next() // 验证通过,继续处理请求

} catch (err) {

if (err.name === 'TokenExpiredError') {

return res.status(401).json({ message: '登录已过期' })

}

return res.status(401).json({ message: '令牌无效' })

}

}

// 2. 权限中间件(验证角色)

const checkRole = (requiredRole) => (req, res, next) => {

if (!req.user) {

return res.status(401).json({ message: '未登录' })

}

if (req.user.role !== requiredRole) {

return res.status(403).json({ message: '无权限访问' })

}

next()

}

// 3. 路由使用示例

router.get('/profile', authMiddleware, getUserProfile)

router.get('/admin', authMiddleware, checkRole('ADMIN'), getAdminData)

// 登录状态接口(供前端路由守卫验证)

router.get('/auth/status', authMiddleware, (req, res) => {

res.json({ isLogin: true, user: req.user })

})

设计思路详解:

中间件模式:认证与权限验证分离,各司其职,便于复用
双重验证:不仅验证令牌签名,还查询数据库确认用户状态(防止注销用户访问)
自动携带:前端无需手动处理令牌,浏览器自动通过 Cookie 发送
权限控制:基于角色的访问控制,细粒度管控资源访问权限
六、信任终止:登出与过期处理
通过清除 Cookie 和前端状态,确保过期 / 注销的令牌无法继续使用。

核心原理
通过清除 Cookie 和前端状态,确保过期 / 注销的令牌无法继续使用。

前端核心代码
// 登出功能

const logout = async () => {

try {

await api.post('/auth/logout') // 调用后端登出接口

} catch (err) {

console.error('登出接口失败:', err)

// 即使接口失败,仍清理前端状态(保障用户体验)

} finally {

const userStore = useUserStore()

userStore.clearUser() // 清理前端状态

router.push('/login') // 跳转登录页

}

}

后端核心代码
// 登出接口(清除令牌)

const logout = (req, res) => {

// 清除HttpOnly Cookie(必须与设置时配置一致)

res.clearCookie('token', {

httpOnly: true,

secure: process.env.NODE_ENV === 'production',

sameSite: 'lax',

})

res.json({ message: '登出成功' })

}

设计思路详解:

状态同步:登出时必须同时清理前端状态和后端 Cookie,避免状态不一致
过期处理:通过 401 响应码统一处理令牌过期,前端拦截后自动清理状态
清除配置:清除 Cookie 的配置必须与设置时完全一致,否则可能清除失败
容错设计:即使后端登出接口失败,前端也应清理状态,保障用户体验
七、完整应用配置

  1. 后端应用入口(app.js)
    const express = require('express')

const cors = require('cors')

const cookieParser = require('cookie-parser')

const authRoutes = require('./routes/authRoutes')

const app = express()

// 中间件配置

app.use(express.json())

app.use(cookieParser())

app.use(

cors({

origin: process.env.CLIENT_URL || 'http://localhost:5173',

credentials: true, // 允许跨域携带Cookie

})

)

// 路由挂载

app.use('/api/auth', authRoutes)

const PORT = process.env.PORT || 3000

app.listen(PORT, () => {

console.log(服务器运行在端口 ${PORT})

})

  1. 环境变量配置(.env)
    DATABASE_URL="file:./dev.db"

JWT_SECRET="your-super-secret-jwt-key"

CLIENT_URL="http://localhost:5173"

NODE_ENV="development"

八、核心安全设计总结

  1. 令牌存储安全
    必须使用 HttpOnly Cookie:禁止 JavaScript 访问,从根源防御 XSS 攻击
    生产环境强制 Secure:确保令牌仅通过 HTTPS 传输
    配置 SameSite 限制:防御 CSRF 攻击
    合理设置有效期:推荐 24 小时内,降低令牌泄露风险
  2. JWT 设计原则
    有效期不宜过长:推荐 24 小时内
    仅包含必要信息:用户 ID、角色等,避免敏感数据
    密钥管理:必须通过环境变量管理,禁止硬编码
    强签名算法:使用 HS256 及以上算法
  3. 密码安全实践
    单向加密存储:使用 bcrypt 等算法,禁止明文或可逆加密
    盐值轮次:推荐 10 轮,平衡安全性和性能
    验证统一错误:避免泄露用户是否存在的信息
  4. 前后端协同安全
    核心验证在后端:前端路由守卫仅做体验优化
    跨域配置正确:必须正确配置 withCredentials 和 CORS
    敏感操作二次验证:不能仅依赖令牌
    数据库层面约束:利用 Prisma 的 unique 约束确保数据完整性
  5. Prisma 安全优势
    天然防 SQL 注入:通过 ORM 的参数化查询
    类型安全:TypeScript 类型检查避免数据类型错误
    字段控制:查询时使用 select 指定返回字段,避免敏感数据泄露
     Ai多模态项目全流程开发中,从需求分析,到Ui设计,程序开发,部署上线,感兴趣打开链接(带项目功能演示),
    https://coding.m.imooc.com/classindex.html?cid=954

九、总结
本文基于 Vue3.5 + Node.js Express + Prisma 实现了完整的登录注册鉴权流程,核心围绕 "信任生命周期" 设计,通过 JWT 令牌和 HttpOnly Cookie 实现安全的身份验证。

这套极简方案的优势在于:

安全性:从令牌存储、密码处理到接口验证,全方位防御常见攻击
开发效率:Prisma 的类型提示和 Vue3.5 的 Composition API 提升开发体验
可扩展性:基于角色的权限设计便于后续功能扩展
用户体验:自动处理令牌携带和过期跳转,减少开发者手动管理成本
实际项目中,可根据需求进一步扩展,如添加验证码、密码重置、多因素认证等功能,但其核心鉴权流程可复用本文设计,是现代 Web 应用鉴权的理想选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/918511.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SPIE出版】第七届地球科学与遥感测绘国际学术会议(GRSM 2025)

第七届地球科学与遥感测绘国际学术会议将于2025年10月17-19日在中国-乌鲁木齐召开。【GRSM 往届均均已全部成功EI检索!】 【下半年遥感测绘、地球科学类重点会议!】 第七届地球科学与遥感测绘国际学术会议(GRSM 202…

ARL(灯塔)安装步骤--超简单!!

ARL(灯塔)安装步骤 在 CentOS 云服务器上安装 ARL(Asset Reconnaissance Lighthouse,资产侦察灯塔系统),可以按照以下步骤进行操作: 前提条件: 确保服务器已安装 Docker 和 Docker Compose 服务器需要有至少 2…

实用指南:Java基础(十四):枚举类详解

实用指南:Java基础(十四):枚举类详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mon…

传统开水壶升级智能水壶低成本开发方案WT588F02KD-32N

今天我们要聊的不是什么高深的半导体制造技术,而是一个更贴近我们日常生活的话题——如何让一个普普通通的便携式烧水壶变得"聪明伶俐",不仅能精准控温,还能开口"说话"告诉你水温。 相信经常出…

一般设计网站页面用什么软件餐饮官网建站模板

今天我们来探讨一个重要的话题&#xff1a;短时间内如何顺利通过 Java 面试&#xff1f; 在此之前&#xff0c;我正在精心编写一套完全面向小白的 Java 自学教程&#xff0c;我相信这套教程会非常适合正在努力提升的你。教程里面涵盖了丰富全面的编程教学内容、详细生动的视频…

做产品网站要备案吗贵阳市建设厅网站

凑算式B DEFA --- ------- 10C GHI&#xff08;如果显示有问题&#xff0c;可以参见【图1.jpg】&#xff09;这个算式中A~I代表1~9的数字&#xff0c;不同的字母代表不同的数字。比如&#xff1a;68/3952/714 就是一种解法&#xff0c;53/1972/486 是另一种解法。…

如何自己做购物网站wordpress linux 安装

娱乐行业的主要组织之一的美国影视演员协会&#xff08;SAG&#xff09;最近因云计算的需要选择Windows Azure解决方案。美国影视演员协会将他们的网站从基于Linux的服务器迁移到支持他们的最大年度事件——美国演员工会奖的Windows Azure上。 每年的年度颁奖典礼的到来标志着一…

基于MATLAB的经典车辆路径问题(VRP)求解方法详解

一、数学模型 经典VRP问题: 给定一个配送中心、多个客户点和若干车辆,要求规划车辆路径,使得所有客户需求被满足且总行驶距离/时间最小。核心约束包括:每个客户仅被访问一次 车辆从配送中心出发并返回 车辆容量限制…

kali复现arp欺骗

利用kali复现arp欺骗 本实验旨在帮助学习者理解 ARP 协议工作机制 以及 常见的ARP欺骗攻击原理与防护方法,仅限于 教学和实验环境 使用。实验过程中可能涉及到网络抓包、ARP表修改、ARP欺骗等操作,这些操作在实际生产…

VGGT: Visual Geometry Grounded Transformer

基于预训练模型的特征重建三维场景,预测多种三维信息。VGGT: Visual Geometry Grounded Transformer VGGT(CVPR25):基于预训练模型抽取特征,通过网络预测3D场景的多种信息。 代码仓库 注:笔者对3D场景重建相关领…

14种纯css3对话气泡样式代码

14种纯css3对话气泡样式代码Posted on 2025-09-26 16:01 且行且思 阅读(0) 评论(0) 收藏 举报<!DOCTYPE html> <html lang="en"><head><meta charset="utf-8" /><…

微信小程序使用地图map 实现定位和实时绘画轨迹

微信小程序使用地图map 实现定位和实时绘画轨迹1 文档官方文档api文档2 准备工作 2.1 注册开发者 1)进入腾讯位置服务官网地址2)注册注册号之后,这个key是需要用到的 2.2 开通地图服务 1)进入微信公众平台-账号设…

嵌入式入门,基于keil5用stm32寄存器和标准库实现LED流水灯

本文要实现的是控制STM32F103C8T6(后文简称STM32)初始化GPIO,并控制其引脚PA0(红色二极管)、PB5(蓝色二极管)和PC13(STM32自带的黄色LED)的高低电平实现LED流水灯。本文章采用的开发板是STM32F103C8T6,下载器是…

AI agent编程随记

ai编程(主要是cursor)确实大大提高了开发的效率,但是一旦在某个问题上无法解决陷入死循环确实很恼火。边用边记录用的经验吧。 1.一定要掌握代码的运行逻辑 最好由自己设计一套方法的运行逻辑由ai帮你快速实现,而不…

小人鱼的数学题 - Li

你这张图片显示的是一道平面解析几何题,题目主要内容如下: 在平面直角坐标系 $xOy$ 中,有一条直线 $y = kx - 3$(其中 $k \neq 0$)和抛物线 $y = -x^2$,它们相交于两点 $A$ 和 $B$,并且点 $A$ 在点 $B$ 的左侧。…

再见 Claude Code!玩转 CodeX CLI 的 16 个实用小技巧,效率拉满!!

大家好,我是R哥。 最近用上了 CodeX CLI,替代了 Claude Code,原因不多说,看这篇:再见 Claude Code,我选择了 Codex!真香!!今天,我再来分享一波我实战中积累的 CodeX CLI 实用小技巧,不管你是新手刚入坑,还…

【IEEE出版】第五届电气工程与机电一体化技术国际学术会议(ICEEMT 2025)

第五届电气工程与机电一体化技术国际学术会议(ICEEMT 2025)定于2025年10月17-19日在广东省深圳市隆重举行。【高层次嘉宾报告:欧洲科学院院士、IEEE Fellow、校长讲座教授、俄罗斯工程院外籍院士,分享研究成果,学…

网站开发前端后端小说网站排名怎么做

来源&#xff1a;科技部网站近日&#xff0c;国家科技部公布了2018年工程和材料领域国家重点实验室评估处理结果。本次64个实验室参加评估&#xff0c;其中工程领域共有43个&#xff0c;材料领域共有21个。评估结果显示&#xff0c;共有6个实验室要求整改&#xff0c;没有实验室…

网站备案承诺书怎么写网站正在建设中 html 模板

目录 1 继承的概念 2 继承的写法 3 子类继承父类的属性和方法 4 子类新增父类没有的属性和方法 5 子类重写父类的属性和方法 6 super超类的使用 7 多继承 1 继承的概念 继承是类与类之间的一种关系&#xff0c;子类继承父类。通过继承可以使得子类能够拥有父类的属性和方…

学习网站开发思路广东省医院建设协会网站首页

简介&#xff1a; 不管是核心大目标&#xff0c;还是O&#xff08;Objectives&#xff09;&#xff0c;或者北极星指标&#xff0c;奇妙等式等等&#xff0c;最后都需要核心组织协同方式来推动整个目标聚焦以及过程的落地。 作为产品经理人&#xff0c;相信很多人都遇到过以下的…