「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