首页 > 其他分享 >二、nextjs API路由如何做好JWT登录鉴权、身份鉴权,joi字段校验,全局处理异常等(c-shopping电商开源)

二、nextjs API路由如何做好JWT登录鉴权、身份鉴权,joi字段校验,全局处理异常等(c-shopping电商开源)

时间:2024-01-23 18:00:45浏览次数:39  
标签:const api req Next return API shopping 电商 鉴权

介绍

在这篇文章中,我们将学习如何在C-Shopping电商开源项目中,基于Next.js 14,处理所有API路由中添加身份验证和错误处理中间件的思路与实现。

这篇文章中的代码片段取自我最近开源项目C-Shopping,完整的项目和文档可在https://github.com/huanghanzhilian/c-shopping地址查看。

Next.js中的API路由

在Next.js14中,/app/api 文件夹包含所有基于文件名路由的api接口

例如文件 /app/api/user/route.js 会自动映射到路由 /api/user。API路由处理程序导出一个默认函数,该函数传递给HTTP请求处理程序。

有关Next.js API路由的更多信息,请参阅 https://nextjs.org/docs/app/building-your-application/routing/route-handlers

官方示例Next.js API 路由处理程序

下面是一个API路由处理程序的基本示例,它将用户列表返回给HTTP GET请求。

只需要导出一个支持HTTP协议名称,再返回一个Response,就完成了一个API

export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY,
    },
  })
  const data = await res.json()
 
  return Response.json({ data })
}

Next.js 自定义编码设计 API处理器

我们会发现,如果按照官方的文档来写API,虽然简单,但是毫无设计感,当面对复杂项目时候很多引用会重复出现,我们需要设计一些中间间,来帮助我们更好的扩展API编码。

为了增加对中间件的支持,我创建了apiHandler包装器函数,该包装器接受一个API处理程序对象,并返回一个HTTP方法(例如GETPOSTPUTDELETE等),再到route文件导出该API,这样就既简单又高效的做好了基础的编码设计。

通过apiHandler包装器函数,再扩展了jwtMiddlewareidentityMiddlewarevalidateMiddlewareerrorHandler,来更好的设计优化代码:

  • jwtMiddleware(处理JWT校验);
  • identityMiddleware(处理身份校验);
  • validateMiddleware(处理 joi,字段校验);
  • errorHandler(全局处理异常)。

项目中的路径 /helpers/api/api-handler.js

import { NextRequest, NextResponse } from 'next/server'

import { errorHandler, jwtMiddleware, validateMiddleware, identityMiddleware } from '.'

export { apiHandler }

function isPublicPath(req) {
  // public routes that don't require authentication
  const publicPaths = ['POST:/api/auth/login', 'POST:/api/auth/logout', 'POST:/api/auth/register']
  return publicPaths.includes(`${req.method}:${req.nextUrl.pathname}`)
}

function apiHandler(handler, { identity, schema, isJwt } = {}) {
  return async (req, ...args) => {
    try {
      if (!isPublicPath(req)) {
        // global middleware
        await jwtMiddleware(req, isJwt)
        await identityMiddleware(req, identity, isJwt)
        await validateMiddleware(req, schema)
      }
      // route handler
      const responseBody = await handler(req, ...args)
      return NextResponse.json(responseBody || {})
    } catch (err) {
      console.log('global error handler', err)
      // global error handler
      return errorHandler(err)
    }
  }
}

users [id] API路由处理程序

下面代码我们可以看到,使用了apiHandler包装器

  • 第一个参数是当前HTTP请求的核心逻辑,解析bodyqueryparams,查询数据,最后通过统一的setJson返回数据结构
  • 第二个参数是一个对象,里面包含了一些中间层扩展参数逻辑,isJwt是否需要JWT校验、schema需要校验的字段和类型、identity操作的用户是否符合权限等。

项目中的路径 /app/api/user/[id]/route.js

import joi from 'joi'

import { usersRepo, apiHandler, setJson } from '@helpers'

const updateRole = apiHandler(
  async (req, { params }) => {
    const { id } = params
    const body = await req.json()
    await usersRepo.update(id, body)

    return setJson({
      message: '更新成功',
    })
  },
  {
    isJwt: true,
    schema: joi.object({
      role: joi.string().required().valid('user', 'admin'),
    }),
    identity: 'root',
  }
)

const deleteUser = apiHandler(
  async (req, { params }) => {
    const { id } = params
    await usersRepo.delete(id)
    return setJson({
      message: '用户信息已经删除',
    })
  },
  {
    isJwt: true,
    identity: 'root',
  }
)

export const PATCH = updateRole
export const DELETE = deleteUser
export const dynamic = 'force-dynamic'

Next.js jwtMiddleware 授权中间件

项目中JWT身份验证中间件是使用jsonwebtoken库来验证发送到受保护API路由的请求中的JWT令牌,如果令牌无效,则抛出错误,导致全局错误处理程序返回401 Unauthorized响应。JWT中间件被添加到API处理程序包装函数中的Next.js请求管道中。

项目中的路径:/api/jwt-middleware.js

import { auth } from '../'

async function jwtMiddleware(req, isJwt = false) {
  const id = await auth.verifyToken(req, isJwt)
  req.headers.set('userId', id)
}
export { jwtMiddleware }

项目中的路径:/helpers/auth.js

import jwt from 'jsonwebtoken'

const verifyToken = async (req, isJwt) => {
  try {
    const token = req.headers.get('authorization')
    const decoded = jwt.verify(token, process.env.NEXT_PUBLIC_ACCESS_TOKEN_SECRET)
    const id = decoded.id
    return new Promise(resolve => resolve(id))
  } catch (error) {
    if (isJwt) {
      throw error
    }
  }
}

const createAccessToken = payload => {
  return jwt.sign(payload, process.env.NEXT_PUBLIC_ACCESS_TOKEN_SECRET, {
    expiresIn: '1d',
  })
}

export const auth = {
  verifyToken,
  createAccessToken,
}

Next.js identityMiddleware 身份校验中间件

在项目设计中,暂时只设计了user普通用户、admin管理员用户,以及一个超级管理员权限root字段,在apiHandler()包装器函数调用时,可以来控制该接口的权限以及身份。

如果权限不匹配,将抛出全局错误,进入Next.js请求管道中,交给全局错误处理程序,从而做到接口异常处理。

项目中的路径:/helpers/api/identity-middleware.js

import { usersRepo } from '../db-repo'

async function identityMiddleware(req, identity = 'user', isJwt = false) {
  if (identity === 'user' && isJwt === false) return

  const userId = req.headers.get('userId')
  const user = await usersRepo.getOne({ _id: userId })
  req.headers.set('userRole', user.role)
  req.headers.set('userRoot', user.root)

  if (identity === 'admin' && user.role !== 'admin') {
    throw '无权操作'
  }

  if (identity === 'root' && !user.root) {
    throw '无权操作,仅超级管理可操作'
  }
}

export { identityMiddleware }

Next.js validateMiddleware 请求参数校验中间件

apiHandler()包装器函数调用时,通过joi工具,schema参数,来指定需要接收和校验的参数,从而避免一些冗余的字段传递,减少异常的发生。

项目中的路径:/helpers/api/validate-middleware.js

import joi from 'joi'

export { validateMiddleware }

async function validateMiddleware(req, schema) {
  if (!schema) return

  const options = {
    abortEarly: false, // include all errors
    allowUnknown: true, // ignore unknown props
    stripUnknown: true, // remove unknown props
  }

  const body = await req.json()
  const { error, value } = schema.validate(body, options)

  if (error) {
    throw `Validation error: ${error.details.map(x => x.message).join(', ')}`
  }

  // update req.json() to return sanitized req body
  req.json = () => value
}

Next.js全局错误处理程序

使用全局错误处理程序捕获所有错误,并消除了在整个Next.js API中重复错误处理代码的需要。

通常按照惯例,'string'类型的错误被视为自定义(特定于应用程序)错误,这简化了抛出自定义错误的代码,因为只需要抛出一个字符串(例如抛出'Username或password is incorrect'),如果自定义错误以'not found'结尾,则返回404响应代码,否则返回标准的400错误响应。

如果错误是一个名为“UnauthorizedError”的对象,则意味着JWT令牌验证失败,因此HTTP 401未经授权的响应代码将返回消息“无效令牌”。

所有其他(未处理的)异常都被记录到控制台,并返回一个500服务器错误响应代码。

项目中的路径:/helpers/api/error-handler.js

import { NextResponse } from 'next/server'
import { setJson } from './set-json'

export { errorHandler }

function errorHandler(err) {
  if (typeof err === 'string') {
    // custom application error
    const is404 = err.toLowerCase().endsWith('not found')
    const status = is404 ? 404 : 400
    return NextResponse.json(
      setJson({
        message: err,
        code: status,
      }),
      { status }
    )
  }

  if (err.name === 'JsonWebTokenError') {
    // jwt error - delete cookie to auto logout
    return NextResponse.json(
      setJson({
        message: 'Unauthorized',
        code: '401',
      }),
      { status: 401 }
    )
  }

  if (err.name === 'UserExistsError') {
    return NextResponse.json(
      setJson({
        message: err.message,
        code: '422',
      }),
      { status: 422 }
    )
  }

  // default to 500 server error
  console.error(err)
  return NextResponse.json(
    setJson({
      message: err.message,
      code: '500',
    }),
    { status: 500 }
  )
}

Next.js 统一处理NextResponse,灵活统一使用setJson

为什么要这样设计?我们不想在每个route中,来回的去引用NextResponse,这会使得代码可读性很差,所以在apiHandler包装器函数中,调用了HTTP handler,拿到了路由管道中想要的数据,最后统一输出。

项目中的路径:/helpers/api/set-json.js

const setJson = ({ code, message, data } = {}) => {
  return {
    code: code || 0,
    message: message || 'ok',
    data: data || null,
  }
}

export { setJson }

至此,我们已经完成了API的设计,这将会给后期的开发带来效率,但同时也带来了代码的难以理解度,只能说设计程序需要有取舍,合适就好。这是我自己基于Next.js Route 的一些设计,也欢迎大家一起通过探讨。

标签:const,api,req,Next,return,API,shopping,电商,鉴权
From: https://blog.51cto.com/u_14229967/9382133

相关文章

  • 【专题】2023年电商行业报告汇总PDF合集分享(附原数据表)
    原文链接:https://tecdat.cn/?p=34900原文出处:拓端数据部落公众号随着全球电商市场在疫情后持续发展,中国市场占据了半壁江山,对全球电商格局产生了重大影响。中国的三至五线城市的城镇人口众多,约占总城镇人口的65%,并且随着移动互联网的普及,这些城市构成了纵深市场,其用户规模正在稳......
  • 基于SSM的社区生鲜电商平台
    本社区生鲜电商平台是以社区生鲜电商平台为事例而开发的,系统以实际运用为开发背景,基于SSM框架,MYSQL数据库设计开发,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得社区生鲜电商平台工作系统化、规范化、高效化。功能有管理员和用户两个角色。管理员功能有个......
  • 【电商数仓6.0】笔记1
    采集项目&数据仓库项目(是企业中数据管理平台中的两个核心管理模块)采集项目:数据采集,传输为主(flume,kafka,datax,maxwell)数据仓库:以计算为主,同时存储数据(mysql,HDFS,Spark,Flink,MR,Hive) 数据库&数据仓库数据库database来源:企业中基础核心的业务数据存储:查找数......
  • sringboot整合shiro实现前后端鉴权控制,标签注解速成(包含常见错误的出现,前后端注解标签
    搭建shiro环境1:导入boot项目中要用到的shiro依赖<!--shiro部分--><!--shiro核心源码--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version......
  • 从零开始:直播电商APP开发全流程解析
    本篇文章,小编将从零开始,全面解析直播电商APP的开发流程,涵盖了关键的技术要点和开发阶段的关键步骤。 第一阶段:需求分析与规划此阶段的关键任务包括:1.用户需求调研2.功能规划3.技术选型第二阶段:设计与原型设计阶段是将需求转化为可执行计划的关键环节。在这一阶段,团队需要完成以下......
  • 跨平台开发:构建适配多设备的直播电商APP
    如今,跨平台开发成为构建适配多设备的直播电商APP的关键之一。本文将深入探讨跨平台开发的优势、选择适当的技术栈以及解决多设备适配的挑战。 一、跨平台开发的优势1.1节省开发成本通过一套代码即可在iOS和Android等多个平台上运行,极大地提高了开发效率。 1.2统一用户体验采用跨......
  • 一、nextjs如何使项目工程化(c-shopping电商开源)
    欢迎来到本系列文章,这些内容都是从我的开源项目C-Shopping衍生而来的。在这个系列中,我们将深入探讨Next.js和其他技术的各个方面,分享我在开发C-Shopping时积累的见解和最佳实践。如果你发现这些文章有帮助,请考虑在GitHub上为项目点亮一颗星星。你的支持对我来说意义重大,也......
  • 18.接口鉴权的多种情况与 解决方案
    接口鉴权是什么 身份认证接口鉴权通用的解决方案 认证信息的获取认证信息的携带@startumlscale800if(登录成功?)then#pink:响应错误;detachendif#palegreen:响应认证信息;#palegreen:携带认证信息发起其他请求;@enduml后端接口鉴权常用......
  • 电商通讯服务
    跨境通讯做跨境通讯的企业一般是可以提供通话话机和呼叫中心软件服务,不仅能接听还能外呼,同时我们提供的服务呼出和接听都为国际当地的号码。通过建立私有网络和全球分布的服务器节点,解决企业原有线路通话质量差、不稳定的问题,提供高质量、安全可靠的语音通信服务,帮助企业在全球范......
  • 开发跨境电商辅助工具,你需要用到这些代码!
    随着跨境电商的日益繁荣,越来越多的开发人员投入到这一领域,开发出各种辅助工具来提高跨境交易的效率和用户体验,本文将为你介绍在开发跨境电商辅助工具时,你需要用到的关键代码。支付接口集成在跨境电商中,支付是最核心的环节之一,为了方便用户完成支付,你需要集成的支付接口,以下是几种常......