在我的服务器中,所有路由都受到保护,并使用 azureAD 护照策略授权不记名令牌。因此,我需要将客户端中生成的访问令牌附加到对服务器的每个请求。我的想法是在创建 axios 实例时将访问令牌附加到不记名标头。我的问题是访问访问令牌。
将令牌保存在本地存储中似乎很方便,但我认为这不太安全。 在 axios 实例创建中获取访问令牌同时还能够处理出现错误的情况的正确方法是什么(因为根据 msal 文档,您不应该在 msal 上下文之外弄乱登录/注销状态)。
我有这个函数可以默默地检索访问令牌:
export const acquireAccessToken = async () => {
const account = msalInstance.getAllAccounts()[0];
if (!account) {
throw new Error("User is not signed in");
}
const request = {
account,
loginHint: account.username,
scopes: ["openid", "profile", "User.Read"],
};
const { accessToken } = await msalInstance.acquireTokenSilent(request);
return accessToken;
};
这个 axios 实例创建函数:
const injectTokenInRequest =
(headers?: RawAxiosRequestHeaders) => async (config: AxiosRequestConfig) => {
try {
const token = await acquireAccessToken();
if (token) {
config.headers = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
};
if (headers) {
Object.assign(config.headers, headers);
}
}
} catch (err) {
console.error(err);
}
return config;
};
export const createAxiosInstance = (
service: string,
prefix: string,
headers?: AxiosRequestHeaders
): AxiosInstance => {
const instance = axios.create({
baseURL: service + prefix,
});
instance.interceptors.request.use(async (config) => {
await injectTokenInRequest({
...headers,
})(config);
return config;
});
return instance;
};
App.tsx:
const SignInButton: FC<{ handleLogin: () => void }> = ({ handleLogin }) => {
return (
<button
onClick={() => {
handleLogin();
}}
>
Sign In
</button>
);
};
const WelcomeUser: FC = () => {
const { accounts } = useMsal();
const username = accounts[0].username;
return <p>Welcome, {username}</p>;
};
const App: FC = () => {
const { instance } = useMsal();
const handleLogin = () => {
instance.loginRedirect();
};
return (
<>
<AuthenticatedTemplate>
<WelcomeUser />
</AuthenticatedTemplate>
<UnauthenticatedTemplate>
<SignInButton handleLogin={handleLogin} />
</UnauthenticatedTemplate>
</>
);
};
你可以用很多不同的方式来做到这一点,所有这些都可以被认为同样“正确”(只要它们有效)。
我通常会做这样的事情(我自己的偏好)
首先,我不会创建 axios 实例,我会在默认实例(只是“axios”)上完成所有操作(但这并不是非常重要)
const UserInterceptorProvider = () => {
const { accounts } = useMsal()
const account = accounts[0]
useEffect(() => {
if(!account) return;
const interceptor = axios.interceptors.request.use(async (config) => {
// request your token and add it to your headers config
// note that you can do this conditionally depending on the URL in the config
// if you're making a request to YOUR backend, then you can get a token
// if you're making an axios request to something other than your BE, you can skip this part and just return the config
});
return () => axios.interceptors.request.eject(interceptor)
},[account,instance])
return null
}
然后在我的应用程序中我会这样做:
const App = () => {
return (
<>
<UserInterceptorProvider/>
....other jsx
</>
)
}
其他人的喜好可能会有所不同,这是我的。当我构建 React 应用程序时,我喜欢尽可能多地“在 React 中”(使用组件)做事情,因为这就是我的想法。我个人发现在“反应之外”做事情(即设置实例)很难理解,因为我的头脑总是在考虑渲染树以及该树中每个组件的职责。