手把手教你搭建NuxtJs框架(二)之使用jwt进行鉴权

鉴权

使用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.界面截图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员ken

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值