「9」next-shopping:实现双token机制

什么是双token机制呢?

即一个accessToken很短时间,一个refreshToken较长时间,我们操作的每一步请求的每一次接口,都需要验证accessToken,如果过期了,就通过refreshToken去刷新拿到一个新的accessToken,这样可以防止accessToken即使泄漏了,攻击者可利用的时间窗口也相对较小,如果refreshToken泄漏了呢,直接在服务端禁止就行了

首先我们将accessToken改为15分钟过期,修改utils/generateToken.js

js 复制代码
import jwt from 'jsonwebtoken'

export const createAccessToken = payload => {
  return jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
    expiresIn: '15m',
  })
}

export const createRefreshToken = payload => {
  return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, {
    expiresIn: '7d',
  })
}

然后改变app/api/auth/accessToken.js的获取refreshToken的refreshtoken为驼峰写法

js 复制代码
import { NextResponse } from 'next/server'
import jwt from 'jsonwebtoken'

import db from '@/lib/db'
import User from '@/models/User'
import sendError from '@/utils/sendError'
import { createAccessToken } from '@/utils/generateToken'

const accessToken = async req => {
  try {
    let { value: rf_token } = req.cookies.get('refreshToken')
    if (!rf_token) return sendError(400, '无刷新token')
    rf_token = JSON.parse(rf_token)
    const result = jwt.verify(rf_token, process.env.REFRESH_TOKEN_SECRET)

    if (!result) return sendError(400, '刷新登录异常')

    await db.connect()
    const user = await User.findById(result.id)
    if (!user) return sendError(res, 400, '此用户不存在')
    await db.disconnect()

    const access_token = createAccessToken({ id: user._id })

    return NextResponse.json(
      {
        access_token,
        refresh_token: rf_token,
        user: {
          name: user.name,
          email: user.email,
          role: user.role,
          avatar: user.avatar,
          root: user.root,
        },
      },
      { statue: 200 }
    )
  } catch (error) {
    return sendError(500, error.message)
  }
}

export const GET = accessToken

改变store/slices/authSlice.js,在登录和退出的reducers中添加对应refreshToken的操作

js 复制代码
import { createSlice } from '@reduxjs/toolkit'
import Cookies from 'js-cookie'

const initialState = {
  userInfo: Cookies.get('userInfo') ? JSON.parse(Cookies.get('userInfo')) : {},
}

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    userLogin: (state, action) => {
      const { user, access_token: token, refresh_token } = action.payload
      state.token = token
      state.user = user
      Cookies.set('userInfo', JSON.stringify({ token, user }))
      Cookies.set('refreshToken', JSON.stringify(refresh_token || ''), { expires: 7 })
    },

    userLogout: (state, action) => {
      Cookies.remove('userInfo')
      Cookies.remove('refreshToken')
      state.token = null
      state.user = null
    },
  },
})

export const { userLogin, userLogout } = authSlice.actions

export default authSlice.reducer

自定义hooks,新建hooks/useRefreshToken.ts

ts 复制代码
'use client'

import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import Cookies from 'js-cookie'

import { userLogin } from '@/store/slices/authSlice'
import { useGetDataQuery } from '@/store/slices/fetchApiSlice'

export default function useRefreshToken() {
  const dispatch = useDispatch()
  let refreshToken = Cookies.get('refreshToken')

  if (refreshToken) {
    refreshToken = JSON.parse(refreshToken)
  }
  console.log('refreshToken', refreshToken, typeof refreshToken, !refreshToken)
  const { data, isSuccess, isLoading, isError, error } = useGetDataQuery(
    {
      url: '/api/auth/accessToken',
      token: '',
    },
    { skip: !refreshToken }
  )

  useEffect(() => {
    if (isSuccess && refreshToken) {
      dispatch(userLogin(data))
    }
    if (isError) {
      console.error('Error refreshing token')
    }
  }, [isSuccess, isError, data, dispatch, refreshToken])
}

主要逻辑是判断cookies里面有没有refreshToken,如果没有的话就跳过useGetDataQuery不请求他,成功了的话就触发登录的reducer

然后我们在hooks/index.js中导出

js 复制代码
export { default as useRefreshToken } from './useRefreshToken'

把他添加到app/(main)/(normal-layout)/layout.js

js 复制代码
'use client'

import { Navbar } from '@/components'
import { useRefreshToken } from '@/hooks'

export default function Layout({ children }) {
  useRefreshToken()
  return (
    <>
      <Navbar />
      {children}
      <footer>footer</footer>
    </>
  )
}

可以看到效果,我们本地有token的时候,会自动请求accessToken接口,拿到用户信息,登录

代码地址:github.com/liyunfu1998...

相关推荐
白兰地空瓶2 小时前
你以为 Props 只是传参? 不,它是 React 组件设计的“灵魂系统”
react.js
程序员码歌3 小时前
短思考第261天,浪费时间的十个低效行为,看看你中了几个?
前端·ai编程
Swift社区3 小时前
React Navigation 生命周期完整心智模型
前端·react.js·前端框架
若梦plus4 小时前
从微信公众号&小程序的SDK剖析JSBridge
前端
用泥种荷花4 小时前
Python环境安装
前端
Light604 小时前
性能提升 60%:前端性能优化终极指南
前端·性能优化·图片压缩·渲染优化·按需拆包·边缘缓存·ai 自动化
Jimmy4 小时前
年终总结 - 2025 故事集
前端·后端·程序员
烛阴4 小时前
C# 正则表达式(2):Regex 基础语法与常用 API 全解析
前端·正则表达式·c#
roman_日积跬步-终至千里4 小时前
【人工智能导论】02-搜索-高级搜索策略探索篇:从约束满足到博弈搜索
java·前端·人工智能
GIS之路5 小时前
GIS 数据转换:使用 GDAL 将 TXT 转换为 Shp 数据
前端