React+Keycloak认证中Redux Saga在Token设置前调用API的问题
解决React+Keycloak刷新页面时Saga提前发起API请求的问题
当前React应用基于Keycloak实现身份认证,正常流程运行正常,但刷新页面后,Redux Saga会在Keycloak Token完成设置前发起API调用,导致请求未携带有效授权Token。以下是几个可行的解决方案:
方案一:在Saga中等待Token就绪
让发起API请求的Saga先等待Redux中auth.token存在后再执行请求,避免提前调用。
修改后的Saga代码
function* waitForValidToken() { // 轮询等待token非空 while (true) { const { auth } = yield select(); if (auth.token) return; yield delay(100); // 短间隔轮询,避免阻塞 } } function* getAPI(action) { // 先等待Token就绪 yield call(waitForValidToken); const { auth } = yield select(); try { // 提前设置授权头,无需作为参数传入axiosRequest setAuthorizationBearer(auth.token); const response = yield call( axiosRequest, "get", BaseURL, `/foo/mini`, {}, null, { sendToken: true }, "application/json" ); yield put({ type: `${action.type}_SUCCESS`, payload: response, metadata: action.metadata }) } catch (e) { yield put({ type: `${action.type}_ERROR`, payload: e }) } }
方案二:基于Keycloak初始化状态控制组件渲染
通过ReactKeycloakProvider的初始化状态,控制触发API请求的组件或Redux Action的执行时机,确保只有Token就绪后才发起请求。
步骤1:添加初始化状态到Redux
// auth reducer const initialState = { token: null, isAuthInitialized: false }; export default function authReducer(state = initialState, action) { switch(action.type) { case 'AUTH_USER': return { ...state, token: action.payload }; case 'AUTH_INITIALIZED': return { ...state, isAuthInitialized: true }; default: return state; } }
步骤2:修改ReactKeycloakProvider配置
const dispatch = useDispatch() const { isAuthInitialized } = useSelector(state => state.auth) <ReactKeycloakProvider onTokens={({token}) => dispatch({ type: 'AUTH_USER', payload: token })} onReady={() => dispatch({ type: 'AUTH_INITIALIZED' })} authClient={Keycloak(config)} initOptions={{ onLoad: 'login-required', checkLoginIframe: false, timeSkew: "0", refreshToken: "" }} LoadingComponent={<div />} > {/* 仅当认证初始化完成后,才渲染触发API请求的内容 */} {isAuthInitialized && <AppMainContent />} </ReactKeycloakProvider>
方案三:直接从Keycloak实例获取Token(推荐)
放弃依赖Redux同步Token的方式,每次API请求时直接从Keycloak客户端实例获取最新Token,从根源上避免时序问题。
步骤1:导出Keycloak实例
// keycloak.js import Keycloak from 'keycloak-js'; const keycloakConfig = { /* 你的Keycloak配置 */ }; export const keycloak = new Keycloak(keycloakConfig);
步骤2:修改Axios请求实例
import axios from "axios"; import { keycloak } from './keycloak'; const instance = ( method, baseURL = process.env.REACT_APP_ENDPOINT, url, data = null, headers = null, sendToken = true, contentType ) => { return new Promise((resolve, reject) => { const reqHeaders = { ...headers }; // 直接从Keycloak实例取Token if (sendToken && keycloak.token) { reqHeaders.Authorization = `Bearer ${keycloak.token}`; reqHeaders["Content-Type"] = contentType || "application/json"; } axios({ method, baseURL, url, data, headers: reqHeaders, timeout: 30000, crossDomain: true, }) .then((payload) => { payload.status < 400 ? resolve(payload) : reject(payload); }) .catch((e) => { if (axios.isCancel(e)) console.log("Request canceled", e.message); reject(e); }); }); }; export default instance;
原代码的小问题修正
原Saga代码中,将setAuthorizationBearer(state.auth.token)作为参数传给axiosRequest是错误的——该函数是同步设置全局变量,无需作为参数传递,应提前调用。
内容的提问来源于stack exchange,提问作者Anass TIssir
相关产品推荐
相关产品推荐

