PHP应用迁移React+SSO后react-router路由异常问题
我来帮你拆解下这个问题的核心:SSO的全局拦截规则、React路由模式混用,以及服务器端缺少前端路由的重写配置,这几个点碰在一起导致了路由失效的问题。下面一步步给你解决:
1. 统一React路由模式,避免混用冲突
你现在的代码里同时用了BrowserRouter(index.js)和HashRouter(App.js),这会直接导致路由解析逻辑混乱,必须先统一成一种模式:
选项A:用BrowserRouter(推荐,更干净的URL)
如果想保留/roster这种无#的美观URL,就全量切换到BrowserRouter,同时调整路由路径配置:
修改App.js,替换路由容器并调整路径:
import React, { Component } from 'react' import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'; // 替换HashRouter为BrowserRouter import Header from './components/header'; import Logout from './components/logout'; import SideBar from './components/sidebar'; import HomeContent from './components/home'; import Ldapuser from './components/ldapuser'; const Main = () => ( <main> <Switch> <Route exact path='/' component={HomeContent}/> // 根路径对应首页,去掉/index.html的冗余路由 <Route path='/home' component={HomeContent}/> <Route path='/logout' component={Logout}/> <Route path='/ldapuser' component={Ldapuser}/> <Route path='/roster' component={/* 你的Roster组件 */}/> // 添加上roster路由 </Switch> </main> ) class App extends Component { render() { return ( <BrowserRouter> {/* 外层统一用BrowserRouter包裹 */} <div> <Header /> <SideBar /> <Main /> </div> </BrowserRouter> ); } } export default App;
再修改index.js,去掉重复的BrowserRouter:
import React, { Component } from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux'; import { createStore, applyMiddleware, combineReducers } from 'redux'; import App from './App' import reducers from './reducers'; require("babel-core/register"); require("babel-polyfill"); const createStoreWithMiddleware = applyMiddleware()(createStore); ReactDOM.render( <Provider store={createStoreWithMiddleware(reducers)}> <App/> {/* App里已经包含BrowserRouter,这里不需要重复包裹 */} </Provider> , document.getElementById('root'));
选项B:用HashRouter(适配SSO的#跳转)
如果没法修改SSO的重定向规则(它会自动给URL加#),那就全量切换到HashRouter,让URL变成/#/roster这种形式,刚好适配SSO的跳转逻辑,避免拦截冲突。
只需要把index.js里的BrowserRouter换成HashRouter,同时App.js里的HashRouter保持一致即可,路由路径不需要做太大调整。
2. 修改.htaccess,适配前端路由和SSO
现在的.htaccess没有处理前端路由的重写,导致直接访问/roster时服务器会去找对应的文件,自然返回404。我们需要添加规则:所有非API、非静态资源的请求,都转发到index.html,让React路由来接管解析:
更新后的.htaccess:
RewriteEngine On # forbidden: malformed query strings RewriteCond %{THE_REQUEST} [\?=][^0-9v] [NC] RewriteRule ^assets/.* - [F] RewriteCond %{THE_REQUEST} [\?=][^0-9.] [NC] RewriteRule ^stylesheets/.* - [F] RewriteCond %{THE_REQUEST} [\?=][^0-9.] [NC] RewriteRule ^javascripts/.* - [F] # forbidden: unnecessary request methods RewriteCond %{REQUEST_METHOD} ^(CONNECT|DEBUG|DELETE|MOVE|PUT|TRACE|TRACK) RewriteRule .* - [F] # 保留API接口转发(包括SSO的/api/user/sso) RewriteRule ^iwsapi/(.*)$ index-api.php?url=$1 [QSA,NC,L] RewriteRule ^api/(.*)$ index-api.php?url=$1 [QSA,NC,L] # 新增:前端路由重写规则 RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.html [L]
这条新增规则会把所有不是真实文件/目录的请求(比如/roster)转发到index.html,让React路由处理剩下的逻辑。
3. 处理SSO重定向后的路由兼容(针对BrowserRouter)
如果用BrowserRouter,SSO认证后会重定向到/index.html/#/roster,我们可以在App初始化时做一个小处理,把带#的路径转换成正常的BrowserRouter路径:
在App组件里添加componentDidMount钩子:
componentDidMount() { // 处理SSO过来的带#的URL const hashPath = window.location.hash.slice(1); if (hashPath && hashPath.startsWith('/')) { window.location.replace(hashPath); // 从/index.html/#/roster跳转到/roster } }
这样用户从SSO跳转过来后,会自动切换到正常的路由路径,React就能正确渲染对应组件了。
4. 验证测试
做完以上修改后,重点测试这几个场景:
- 直接访问
/roster:应该触发SSO认证,认证后跳转到对应的React组件 - 访问
/index.html后点击内部链接:路由正常跳转无卡顿 - 刷新页面:路由保持有效,不会重复触发SSO拦截(已认证状态下)
内容的提问来源于stack exchange,提问作者shorif2000

