React客户端自动重定向IdentityServer4及用户信息获取异常求助
React集成IdentityServer4的问题排查
问题背景
我开发了一个简单的React应用,尝试与IdentityServer4进行集成。应用包含两个页面:Home和My Home。
预期流程
- 未授权用户访问根路径(
/,即Home页)时,需点击登录按钮,跳转至IdentityServer4的登录页面。 - 登录完成后,跳转回
My Home页(/myHome)。 - 在
My Home页中,可调用API获取用户信息,还有一个登出按钮。
当前遇到的问题
- 访问根路径
/时,会自动重定向至IdentityServer4的登录页面,无需点击登录按钮。 - 登录认证完成后,成功跳转至
My Home页,但调用获取用户信息时,控制台报错,提示profile为null且No matching state found in storage。
怀疑是重定向函数或配置存在问题,希望得到帮助。
相关代码片段
App.js
import React, { useEffect } from 'react'; import { Route, Routes } from 'react-router-dom'; import { AuthProvider } from 'oidc-react'; import SigninOidc from "./components/SiginOidc"; import SignoutOidc from "./components/SignoutOidc"; import { Provider } from 'react-redux'; import userManager, { loadUserFromStorage } from './services/userService'; import store from './store'; import MyHome from './components/MyHome'; import Home from './components/Home'; const App = () => { useEffect(() => { loadUserFromStorage(store) }, []) return ( <Provider store={store}> <AuthProvider userManager={userManager} store={store}> <Routes> <Route path='/' element={<Home />}></Route> <Route path='/myHome' element={<MyHome /> }></Route> <Route path="/signin-oidc" element={<SigninOidc />}></Route> <Route path="/signout-oidc" element={<SignoutOidc /> }></Route> </Routes> </AuthProvider> </Provider> ); }; export default App;
Home.js
import React from 'react'; import { useSelector } from 'react-redux'; import { signinRedirect } from '../services/userService'; import { Navigate } from 'react-router-dom'; const Home = () => { const user = useSelector(state => state.auth.user); function login() { signinRedirect(); } return ( (user) ? (<Navigate to='/myHome' />) : ( <div> <h1>Home</h1> <p>Hello stranger, please log in</p> <button className="button button-clear" onClick={() => login()}>Login</button> </div>) ); } export default Home;
MyHome.js
import React from 'react'; import { useState } from 'react'; import { useSelector } from 'react-redux'; import { signoutRedirect } from '../services/userService'; import * as apiService from '../services/apiService'; const MyHome = () => { const user = useSelector(state => state.auth.user); const [name, setName] = useState(""); function logout() { signoutRedirect(); } async function getUserInfo() { const userId = user.profile.sub; const profile = await apiService.getUserInfo(userId); setName(profile.displayName); } return ( <div> <h1>Hello member</h1> <button onClick={() => await getUserInfo() }>Get user name: { name}</button> <button onClick={() => logout() }>Logout</button> </div> ); } export default MyHome;
SigninOidc.js
import React, { useEffect } from 'react'; import { signinRedirectCallback } from '../services/userService'; import { useNavigate } from 'react-router-dom'; function SigninOidc() { const navigate = useNavigate(); useEffect(() => { async function signinAsync() { await signinRedirectCallback(); navigate(`/myHome`); } signinAsync(); }, [navigate]) return ( <div> Redirecting... </div> ) } export default SigninOidc;
SignoutOidc.js
import React, { useEffect } from 'react'; import { signoutRedirectCallback } from '../services/userService'; import { useNavigate } from 'react-router-dom'; function SignoutOidc() { const navigate = useNavigate(); useEffect(() => { async function signoutAsync() { await signoutRedirectCallback(); navigate('/myHome'); } signoutAsync(); }, [navigate]) return ( <div> Redirecting... </div> ) } export default SignoutOidc;
userService.js
import { UserManager, WebStorageStateStore } from 'oidc-client'; import { storeUserError, storeUser } from '../actions/authActions' const config = { authority: "http://localhost:5001", client_id: "partie.reactwebapp.client", redirect_uri: "http://localhost:44450/signin-oidc", response_type: "id_token token", scope: "openid profile gateway_api", post_logout_redirect_uri: "http://localhost:44450/signout-oidc", loadUserInfo: true, automaticSilentRenew: true, acr_values: '', userStore: new WebStorageStateStore({ store: window.localStorage }), metadata: { issuer: "http://localhost:5001", authorization_endpoint: 'http://localhost:5001/connect/authorize', token_endpoint: 'http://localhost:5001/connect/token', jwks_uri: 'http://localhost:5001/.well-known/openid-configuration/jwks', userinfo_endpoint: 'http://localhost:5001/connect/userinfo', end_session_endpoint: 'http://localhost:5001/connect/endsession' } }; const userManager = new UserManager(config); export async function loadUserFromStorage(store) { try { let user = await userManager.getUser(); if (!user) { return store.dispatch(storeUserError()) } store.dispatch(storeUser(user)); } catch (e) { console.error(`User not found: ${e}`); store.dispatch(storeUserError()); } } export function signinRedirect() { return userManager.signinRedirect(); } export async function signinRedirectCallback() { return await userManager.signinRedirectCallback(); } export function signoutRedirect() { userManager.clearStaleState(); userManager.removeUser(); return userManager.signoutRedirect(); } export function signoutRedirectCallback() { userManager.clearStaleState(); userManager.removeUser(); return userManager.signoutRedirectCallback(); } export default userManager;
问题分析与修复方案
问题1:访问根路径自动跳转登录页
原因:oidc-react的AuthProvider默认开启自动登录检测,未认证时会直接触发重定向。
修复:在AuthProvider中添加autoSignIn={false}禁用自动登录:
<AuthProvider userManager={userManager} store={store} autoSignIn={false}>
问题2:profile为null和No matching state found in storage错误
原因1:登录回调未同步用户到Redux
signinRedirectCallback返回用户对象,但未更新Redux store,导致MyHome读取的user缺少profile信息。
修复:修改SigninOidc.js,将回调获取的用户存入Redux:
import React, { useEffect } from 'react'; import { signinRedirectCallback } from '../services/userService'; import { useNavigate } from 'react-router-dom'; import { storeUser } from '../actions/authActions'; import { useDispatch } from 'react-redux'; function SigninOidc() { const navigate = useNavigate(); const dispatch = useDispatch(); useEffect(() => { async function signinAsync() { const user = await signinRedirectCallback(); dispatch(storeUser(user)); navigate(`/myHome`); } signinAsync(); }, [navigate, dispatch]) return ( <div> Redirecting... </div> ) } export default SigninOidc;
原因2:登出时提前清除状态
signoutRedirect中先调用removeUser()清除了存储的state,导致IdentityServer回调时无法匹配状态。
修复:调整signoutRedirect顺序,先触发重定向再清除状态:
export function signoutRedirect() { userManager.clearStaleState(); return userManager.signoutRedirect(); }
同时修改signoutRedirectCallback在回调后清除用户:
export async function signoutRedirectCallback() { await userManager.signoutRedirectCallback(); userManager.clearStaleState(); userManager.removeUser(); }
原因3:MyHome按钮点击语法错误
按钮onClick中直接使用await未包裹异步函数,导致语法错误。
修复:修改按钮点击事件:
<button onClick={async () => await getUserInfo() }>Get user name: { name}</button>
或直接调用异步函数:
<button onClick={getUserInfo}>Get user name: { name}</button>
原因4:错误状态未正确设置用户为null
storeUserError需确保将Redux中的auth.user设置为null,避免Home组件误判用户已登录。
示例reducer逻辑:
function authReducer(state = { user: null }, action) { switch(action.type) { case 'STORE_USER': return { ...state, user: action.payload }; case 'STORE_USER_ERROR': return { ...state, user: null }; default: return state; } }
内容的提问来源于stack exchange,提问作者Hoang Minh
相关产品推荐
相关产品推荐

