「9」next-shopping:实现双token机制
什么是双token机制呢?
即一个accessToken很短时间,一个refreshToken较长时间,我们操作的每一步请求的每一次接口,都需要验证accessToken,如果过期了,就通过refreshToken去刷新拿到一个新的accessToken,这样可以防止accessToken即使泄漏了,攻击者可利用的时间窗口也相对较小,如果refreshToken泄漏了呢,直接在服务端禁止就行了
首先我们将accessToken改为15分钟过期,修改utils/generateToken.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为驼峰写法
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的操作
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
'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中导出
export { default as useRefreshToken } from './useRefreshToken'
把他添加到app/(main)/(normal-layout)/layout.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接口,拿到用户信息,登录

转载自:https://juejin.cn/post/7357994849386217524




