接口用户权限校验逻辑 (jsonwebtoken使用)

我们很多应用都是有用户权限的,如果校验通过,接口就返回数据;否则就提示401未授权。这里面逻辑是怎样的,这篇笔记介绍如何使用jsonwebtoken 进行鉴权

JWT 的流程本质就是一句话:

服务端生成 Token 给客户端,客户端之后每次请求带上 Token,服务端验证 Token 来决定是否放行。

  1. 客户端发送用户信息
  2. 服务端校验用户信息,生成token, 把token返回客户端
  3. 客户端请求带上token, config.headers.Authorization = Bearer ${token}
  4. 服务端验证token, 如果通过就放行 jwt.verify(token, secret)
校验的业务流程
  1. 登录阶段
  • 客户端(浏览器/app)把用户名和密码发给服务端
  • 服务端验证通过后,生成一个JWT Token, 把用户信息(比如userId)编码进去,然后签名
  • 服务端把这个Token返回给客户端
  1. 后续请求阶段
  • 客户端把这个Token保存起来(通常是放在localStorage或cookie里)
  • 每次发请求时,把这个Token放在请求头里
js 复制代码
Authorization: Bearer <token>
  1. 服务端验证阶段
  • 服务端拿到这个Token, 用之前的密匙(secret)验证签名
  • 如果验证成功,就可以找到这个请求是谁发的(从payload中解析出用户信息),并放行
  • 如果Token无效或过期,就拒绝请求(通常返回401未授权)
代码具体实现

我们后端使用的是express, 安装jsonwebtoken包

bash 复制代码
npm i jsonwebtoken

前端客户端发送用户信息 (下面代码是用户注册的时候)

js 复制代码
 const handleSignUp = async e => {
    e.preventDefault()
    if (!name) {
      setError('请输入你的名字')
      return
    }
    if (!validateEmail(email)) {
      setError('请输入一个有效的邮件地址')
      return
    }
    if (!password) {
      setError('请输入密码')
      return
    }
    setError('')
    // Signup API Call
    try {
      const res = await axiosInstance.post('/create-account', {
        fullName: name,
        email,
        password,
      })
      // Handle successful login response
      if (res.data && res.data.accessToken) {
        localStorage.setItem('token', res.data.accessToken)
        navigate('/dashboard')
      }
    } catch (error) {
      setError(error?.response?.data?.message || '未知错误.')
    } finally {
      setLoading(false)
    }
  }

在首页,处理用户请求,拿到用户信息,校验通过就生成签名

js 复制代码
const jwt = require('jsonwebtoken')
const express = require('express')
const cors = require('cors')
const bcrypt = require('bcrypt')
const User = require('./models/user.model')
const app = express()

app.use(express.json())
app.use(cors({ origin: '*' }))

app.post('/create-account', async (req, res) => {
  const { fullName, email, password } = req.body

  if (!fullName || !email || !password) {
    return res.status(400).json({
      error: true,
      message: '所有字段必填',
    })
  }

  const isUser = await User.findOne({ email })
  if (isUser) {
    return res.status(400).json({
      error: true,
      message: '用户已经存在',
    })
  }

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

  const user = new User({
    fullName,
    email,
    password: hashedPassword,
  })

  await user.save()

  const accessToken = jwt.sign(
    { userId: user._id },
    process.env.ACCESS_TOKEN_SECRET,
    {
      expiresIn: '72h',
    }
  )
  return res.status(201).json({
    error: false,
    user: { fullName: user.fullName, email: user.email },
    accessToken,
    message: 'Registration successful',
  })
})

注册通过后,服务端把生成的token返回给客户端,客户端获取签名,存在localStorage里

js 复制代码
if (res.data && res.data.accessToken) {
    localStorage.setItem('token', res.data.accessToken)
    navigate('/dashboard')
  }

然后在接下来需要权限的的请求里,都把这个token带上, axiosInstance.ts

js 复制代码
import axios from 'axios'

const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  }
})

axiosInstance.interceptors.request.use(
  config => {
    const accessToken = localStorage.getItem('token')
    if (accessToken) {
      config.headers.Authorization = `Bearer ${accessToken}`
    }
    return config
   },
   error => {
    return Promise.reject(error)
   }
)
export default axiosInstance

接着我们在服务端处理用户发过来的请求

php 复制代码
const { authenticateToken } = require('./utilities')

app.get('/get-all-stories', authenticateToken, async (req, res) => {
  const { userId } = req.user
  try {
    const travelStories = await TravelStory.find({ userId: userId }).sort({
      isFavourite: -1,
    })
    res.status(200).json({ stories: travelStories })
  } catch (error) {
    res.status(500).json({ error: true, message: error.message })
  }
})

注意这里的authenticateToken, 我们先是获取req.headers['authorization'], 这里a是小写,大小写没关系,(在 HTTP 协议中头部字段名不区分大小写,客户端用大写、服务端用小写是可行的),利用jwt.verify 进行验证,验证通过了就会继续执行请求

scss 复制代码
const jwt = require('jsonwebtoken')

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization']
  const token = authHeader && authHeader.split(' ')[1]

  // No token, unauthorized
  if (!token) return res.sendStatus(401)

  // Verify token
  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
    // Token invalid, forbidden
    if (err) return res.sendStatus(401)
    req.user = user
    next()
  })
}

module.exports = {
  authenticateToken,
}
one more thing

这段代码里的'application/json' 和 app.use(express.json())的作用分别是什么,有关联吗

js 复制代码
// 前端接口拦截
const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  }
})

// 服务端设置接口处理请求参数
app.use(express.json())

'Content-Type': 'application/json' 的作用是告诉服务器:我发送的数据是 JSON 格式

js 复制代码
axiosInstance.post('/create-account', {
    fullName: '孙悟空',
    email: '[email protected]',
    password: 'qitiandasheng',
})

配合 'Content-Type': 'application/json',axios 会把数据变成这样发送给服务器:

http 复制代码
POST /login HTTP/1.1

Content-Type: application/json
{
    "fullName": "孙悟空",
    "email": "[email protected]",
    "password": "qitiandasheng",
}

app.use(express.json())就是告诉服务器: 如果客户端发过来的数据是 JSON,我能自动解析并放到 req.body 里。就是说前面发的孙悟空信息,可以直接拿到

perl 复制代码
app.post('/login', (req, res) => {
  console.log(req.body); // { "fullName": "孙悟空","email": "[email protected]", "password": "qitiandasheng"}
});

如果前端用的是 application/x-www-form-urlencoded

js 复制代码
axios.post('/login', qs.stringify({ username: '悟空', password: 'wukong' }), {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
});

这个时候,数据会以这种格式发送给服务端(key=value&key=value 的形式):

js 复制代码
username=%E6%82%9F%E7%A9%BA&password=wukong
请求头 Content-Type 前端格式 服务端解析方法 req.body 是否能正常读取
application/json JSON.stringify(data) express.json() ✅ 是
application/x-www-form-urlencoded qs.stringify(data) express.urlencoded({ extended: true }) ✅ 是
没有设置或格式不符 自动 fallback 或出错 无匹配中间件,Express 不会解析 ❌ 否,req.bodyundefined

总结

前端的 Content-Type: application/json 是说"我发的是 JSON",后端的 express.json() 是说"我能识别 JSON"。它们必须一起使用,数据才能顺利沟通

相关推荐
还是鼠鼠6 分钟前
Node.js 跨域 CORS 简单请求与预检请求的介绍
运维·服务器·vscode·中间件·node.js·express
恋猫de小郭8 分钟前
Android Studio Cloud 正式上线,不只是 Android,随时随地改 bug
android·前端·flutter
我命由我123451 小时前
35.Java线程池(线程池概述、线程池的架构、线程池的种类与创建、线程池的底层原理、线程池的工作流程、线程池的拒绝策略、自定义线程池)
java·服务器·开发语言·jvm·后端·架构·java-ee
whoarethenext4 小时前
qt的基本使用
开发语言·c++·后端·qt
清岚_lxn5 小时前
原生SSE实现AI智能问答+Vue3前端打字机流效果
前端·javascript·人工智能·vue·ai问答
ZoeLandia5 小时前
Element UI 设置 el-table-column 宽度 width 为百分比无效
前端·ui·element-ui
橘子味的冰淇淋~6 小时前
解决 vite.config.ts 引入scss 预处理报错
前端·vue·scss
小小小小宇7 小时前
V8 引擎垃圾回收机制详解
前端
lauo8 小时前
智体知识库:ai-docs对分布式智体编程语言Poplang和javascript的语法的比较(知识库问答)
开发语言·前端·javascript·分布式·机器人·开源
草捏子8 小时前
主从延迟导致数据读不到?手把手教你架构级解决方案
后端