You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:15:08