鉴权
使用middleware(中间件)进行鉴权
我们在项目根目录下创建middleware文件夹,创建auth.ts文件,这个就是一个简单的“鉴权文件”,内容如下:
export default defineNuxtRouteMiddleware((to, from) => {
// isAuthenticated()是一个验证用户是否已经认证的示例方法
//
if (isAuthenticated() === false) {
return navigateTo('/login')
}
})
function isAuthenticated() {
return true;
}
这边isAuthenticated函数默认返回true,意味着权限一直是够的,假设不够会跳转到/login这个路由(登录页)。
接着我们在pages目录下创建一个页面:dashboard.vue
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>
<!-- 这边验证了 权限 所以第一步必须是登录用户 -->
<template>
<h1>欢迎来到您的仪表盘</h1>
</template>
没错你看到有个 definePageMeta({middleware: ‘auth’}),会调用中间件里面的auth,对应的就是auth.js。当我们访问这个页面(localhost:3001/dashboard)时你会发现页面不会调整,此时你将isAuthenticated函数的返回值设置成false,在访问你会发现由于鉴权失败会跳往登录页.
使用后端接口进行鉴权
前面的这种方式好像更像个“把戏”似的,因为权限并没有真正获取到。这里我们使用后端接口进行鉴权,这边我们需要使用sidebase-auth这个模块来进行鉴权
主要依赖如下:
"dependencies": {
"@sidebase/nuxt-auth": "^0.9.3",
"jsonwebtoken": "^9.0.2",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.7"
}
主要是:
sidebase-auth: 0.9.3
jsonwebtoken: 9.0.2
zod: 3.23.8
@types/jsonwebtoken: 9.0.7
这边使用jsonwebtoken就是计算和存储jwt信息的,这边大家仔细看有个devDependencies,@types/jsonwebtoken这个库是被安装在开发环境依赖中的,npm不会去管他的。只是在我们的包在本地开发的时候使用的。
so,错误的安装命令:
npm install sidebase-auth(x)
npm install zod (√)
npm install jsonwebtoken(√)
npm install @types/jsonwebtoken(x)
正确的安装命令:
npx nuxi@latest module add sidebase-auth(√)//这边应该是向项目增加模块
npm install zod (√)
npm install jsonwebtoken(√)
npm i --save-dev @types/jsonwebtoken(√)
这里我看到github的demo,有这个jsonwebtoken模块安装 @types/jsonwebtoken后发现项目根本不能运行,使用了npm i --save-dev @types/jsonwebtoken 将其安装到开发环境才解决问题。
接着我直接使用github上的demo: server有这几个服务
代码如下:
import { createError, eventHandler, readBody } from 'h3'
import { z } from 'zod'
//import { jwt } from 'jsonwebtoken'
//var jwt = require('jsonwebtoken');
//import jwt from 'jsonwebtoken'
import jwt from 'jsonwebtoken'
/*
* DISCLAIMER!
* This is a demo implementation, please create your own handlers
*/
/**
* This is a demo secret.
* Please ensure that your secret is properly protected.
*/
export const SECRET = 'dummy'
/** 30 seconds */
export const ACCESS_TOKEN_TTL = 30
export interface User {
username: string
name: string
picture: string
}
export interface JwtPayload extends User {
scope: Array<'test' | 'user'>
exp?: number
}
interface TokensByUser {
access: Map<string, string>
refresh: Map<string, string>
}
/**
* Tokens storage.
* You will need to implement your own, connect with DB/etc.
*/
export const tokensByUser: Map<string, TokensByUser> = new Map()
/**
* We use a fixed password for demo purposes.
* You can use any implementation fitting your usecase.
*/
const credentialsSchema = z.object({
username: z.string().min(1),
password: z.literal('hunter2')
})
export default eventHandler(async (event) => {
const result = credentialsSchema.safeParse(await readBody(event))
//密码:hunter2
if (!result.success) {
throw createError({
statusCode: 403,
statusMessage: 'Unauthorized, hint: try `hunter2` as password'
})
}
// Emulate login
const { username } = result.data
const user = {
username,
picture: 'https://github.com/nuxt.png',
name: `User ${username}`
}
const tokenData: JwtPayload = { ...user, scope: ['test', 'user'] }
const accessToken = jwt.sign(tokenData, SECRET, {
expiresIn: ACCESS_TOKEN_TTL
})
const refreshToken = jwt.sign(tokenData, SECRET, {
// 1 day
expiresIn: 60 * 60 * 24
})
// Naive implementation - please implement properly yourself!
const userTokens: TokensByUser = tokensByUser.get(username) ?? {
access: new Map(),
refresh: new Map()
}
userTokens.access.set(accessToken, refreshToken)
userTokens.refresh.set(refreshToken, accessToken)
tokensByUser.set(username, userTokens)
return {
token: {
accessToken,
refreshToken
}
}
})
export function extractToken(authorizationHeader: string) {
return authorizationHeader.startsWith('Bearer ')
? authorizationHeader.slice(7)
: authorizationHeader
}
import { eventHandler } from 'h3'
export default eventHandler(() => ({ status: 'OK ' }))
import { createError, eventHandler, getRequestHeader, readBody } from 'h3'
import jwt from 'jsonwebtoken'
import { type JwtPayload, SECRET, type User, extractToken, tokensByUser } from './login.post'
/*
* DISCLAIMER!
* This is a demo implementation, please create your own handlers
*/
export default eventHandler(async (event) => {
const body = await readBody<{ refreshToken: string }>(event)
const authorizationHeader = getRequestHeader(event, 'Authorization')
const refreshToken = body.refreshToken
if (!refreshToken || !authorizationHeader) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized, no refreshToken or no Authorization header'
})
}
// Verify
const decoded = jwt.verify(refreshToken, SECRET) as JwtPayload | undefined
if (!decoded) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized, refreshToken can\'t be verified'
})
}
// Get tokens
const userTokens = tokensByUser.get(decoded.username)
if (!userTokens) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized, user is not logged in'
})
}
// Check against known token
const requestAccessToken = extractToken(authorizationHeader)
const knownAccessToken = userTokens.refresh.get(body.refreshToken)
if (!knownAccessToken || knownAccessToken !== requestAccessToken) {
console.log({
msg: 'Tokens mismatch',
knownAccessToken,
requestAccessToken
})
throw createError({
statusCode: 401,
statusMessage: 'Tokens mismatch - this is not good'
})
}
// Invalidate old access token
userTokens.access.delete(knownAccessToken)
const user: User = {
username: decoded.username,
picture: decoded.picture,
name: decoded.name
}
const accessToken = jwt.sign({ ...user, scope: ['test', 'user'] }, SECRET, {
expiresIn: 60 * 5 // 5 minutes
})
userTokens.refresh.set(refreshToken, accessToken)
userTokens.access.set(accessToken, refreshToken)
return {
token: {
accessToken,
refreshToken
}
}
})
import { createError, eventHandler, getRequestHeader } from 'h3'
import jwf from 'jsonwebtoken'
import { type JwtPayload, SECRET, extractToken, tokensByUser } from './login.post'
export default eventHandler((event) => {
const authorizationHeader = getRequestHeader(event, 'Authorization')
if (typeof authorizationHeader === 'undefined') {
throw createError({ statusCode: 403, statusMessage: 'Need to pass valid Bearer-authorization header to access this endpoint' })
}
const extractedToken = extractToken(authorizationHeader)
let decoded: JwtPayload
try {
decoded = jwf.verify(extractedToken, SECRET) as JwtPayload
}
catch (error) {
console.error({
msg: 'Login failed. Here\'s the raw error:',
error
})
throw createError({ statusCode: 403, statusMessage: 'You must be logged in to use this endpoint' })
}
// Check against known token
const userTokens = tokensByUser.get(decoded.username)
if (!userTokens || !userTokens.access.has(extractedToken)) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized, user is not logged in'
})
}
// All checks successful
const { username, name, picture, scope } = decoded
return {
username,
name,
picture,
scope
}
})
这边我重点说说login.post.ts里面的一些内容,首先在nuxt.config.ts我们要加上如下配置:
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
//新增模块 nuxt-auth
modules: ['@sidebase/nuxt-auth'],
auth: {
provider: {
type: 'local',
endpoints: {
getSession: { path: '/user' }
},
pages: {
login: '/',//需要登陆时会往‘/’这个路由跳
},
token: {
signInResponseTokenPointer: '/token/accessToken'
},
session: {
dataType: { id: 'string', email: 'string', name: 'string', role: '\'admin\' | \'guest\' | \'account\'', subscriptions: '{ id: number, status: \'ACTIVE\' | \'INACTIVE\' }[]' },
dataResponsePointer: '/'
},
refresh: {
// This is usually a static configuration `true` or `false`.
// We do an environment variable for E2E testing both options.
isEnabled: process.env.NUXT_AUTH_REFRESH_ENABLED !== 'false',
endpoint: { path: '/refresh', method: 'post' },
token: {
signInResponseRefreshTokenPointer: '/token/refreshToken',
refreshRequestTokenPointer: '/refreshToken'
},
}
},
sessionRefresh: {
// Whether to refresh the session every time the browser window is refocused.
enableOnWindowFocus: true,
// Whether to refresh the session every `X` milliseconds. Set this to `false` to turn it off. The session will only be refreshed if a session already exists.
enablePeriodically: 5000,
// Custom refresh handler - uncomment to use
// handler: './config/AuthRefreshHandler'
},
globalAppMiddleware: {
isEnabled: false
}
},
})
在auth节点下我配置了个provider, type是“local"意味着鉴权是对系统内部鉴权,不是对接外部 比如登录 华为云/支付宝/github等等,endpoints节点配置了getSession 意味着获取session的路径是通过/user。
在login.post.ts文件中
这个是使用’hunter2’作为固定的密码 ,账号其实可以随意点 ,比如 userName、admin 或者test123
/**
* We use a fixed password for demo purposes.
* You can use any implementation fitting your usecase.
*/
const credentialsSchema = z.object({
username: z.string().min(1),
password: z.literal('hunter2')
})
在这个接口里面设置了jwt的失效时间60 * 60 * 24 (s),也就是一天的时间。假设你真的想用此框架做你的服务器,后期你可以把测试的部分使用数据库查询,进行用户登录校验。
效果演示
1.运行项目
2.界面截图