问题复现:
连接钱包后,会调用函数,弹出窗口让用户签名
if (signatureMessage) {
await signMessageAsync({
message: signatureMessage,
});
dispatch(changeHasLoggedToTrue());
}
有了签名后,会调用signIn来设置本地存储
signIn函数
javascript"> const signIn = async (signature) => {
try {
const signinRes = await API.post(`/auth/signin`, {
address: address,
signature: signature,
});
const a***essToken = signinRes.data?.data?.a***ess_token;
setLocalStorage("a***essToken", a***essToken);//设置本地存储
refreshAPIToken();
addressInfo.current.address = address;
} catch (err) {
console.log("signin");
showMessage({
type: "error",
title: "Failed to sign-in",
body: err.message,
});
disconnect();
}
};
如果用户连接了钱包就会调接口,获取用户是否是管理员 通过RTK(redux toolkit)存储在isAdmin变量里
const res = await API.get(`/member/${address}`);
if (!res.data.data) {
dispatch(changeIsAdminToFalse());
return;
}
if (res.data.data.role.includes("Member")) {
dispatch(changeIsMemberToTrue());
}
当isAdmin为true的时候,才显示member列表
后端接口也限制了,不是管理员拿不到数据,而我明明用管理员登入了,还是拿不到,刷新才拿得到数据。不刷新的时候控制台报401,Unauthorized,没有权限。
解决过程
于是问了gpt
gpt给了5种可能的原因
关键是第一条 问题定位到了没有a***essToken 但是我一看控制台Application里面 a***ess token没错,是有的,但是数据要刷新页面才出来,于是我继续问
我:the a***ess token is right , we can get the requested source by this token after refresh. But how to get the requested source without refresh
GPT:If you’re able to a***ess the resource after refreshing the page, it’s possible that the a***ess token is not being sent with subsequent requests after the initial connection is made.
To ensure that the a***ess token is being sent with all subsequent requests, you can set the token in the headers of the Axios instance. Here’s an example of how to do this:
翻譯:刷新后才出现数据,可能是因为在发送请求的时候a***ess token没有被发送,为了确保所有的请求都带有a***ess token 需要给axios实例设置header
import axios from 'axios';
const API = axios.create({
baseURL: 'https://example.***/api',
headers: {
Authorization: `Bearer ${getLocalStorage('a***essToken')}`,
},
});
API.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const a***essToken = await handleRefreshToken();
API.defaults.headers.***mon.Authorization = `Bearer ${a***essToken}`;
return API(originalRequest);
}
return Promise.reject(error);
},
);
GPT给了一段代码 我自己再看逻辑 修改 关键就是当服务器返回401的时候,要给axios请求设置a***essToken,再次发送请求;
但是这种有漏洞,
1.每次请求刚开始都返回401,request拦截之后,再次发送请求才拿到数据
2.如果a***essToken过期了,请求不到数据,需求是要清除本地缓存并退出钱包登录
第2条是新增的需求,如何判断a***essToken过期,GPT说如果a***essToken过期,服务器会返回401,所以这里if判断服务器会返回401后的逻辑不能是重新发送请求。首先要解决第一个问题。为何每次请求都返回401?
打log,发现在连接钱包set token,token有值之前,就发送请求,所以要在连接钱包set token,确保token有值之后才发送请求,之前是判断为管理员就发送请求,这个在需求层面来将没错,但是connect wallet 设置isAdmin为true的时候还没有set token 所以在 set token之后新增 dispatch(changeSigninToTrue());
useEffect监听hasSiginin:
useEffect(() => {
if (hasSiginin) {
console.log("有發送請求");
searchList();
}
}, []);
最终代码:
import axios from "axios";
import { getLocalStorage, removeLocalStorage } from "@/src/utils/utility";
import { useLogout } from "../src/hooks/useLogout";
const API = axios.create({
baseURL: process.env.NEXT_PUBLIC_FRENART_BACKEND_API,
});
API.interceptors.request.use(
(config) => {
const a***essToken = refreshAPIToken();
if (a***essToken) {
config.headers.Authorization = `Bearer ${a***essToken}`; //a***essToken
}
return config;
},
(error) => Promise.reject(error)
);
function handleUnauthorizedError() {
const logout = useLogout();
logout();
}
API.interceptors.response.use(
(response) => response,
async (error) => {
const a***essToken = refreshAPIToken();
error.config.headers.Authorization = `Bearer ${a***essToken}`;
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
console.log("401");
handleUnauthorizedError();
}
return Promise.reject(error);
}
);
function refreshAPIToken() {
const a***essToken = getLocalStorage("a***essToken");
API.defaults.headers.***mon.Authorization = `Bearer ${a***essToken}`;
return a***essToken;
}
export { refreshAPIToken };
export default API;
之后管理员连接钱包,就能拿到数据了,因为发送请求的时候 header里面有了Authorization,即有了a***essToken