如何在 React Router 4 中实现经过身份验证的路由?
How to implement authenticated routes in React Router 4?
我试图实现经过身份验证的路由,但发现 React Router 4 现在阻止了它的工作:
<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
<Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
<Route exact path="/domains" component={DomainsIndex} />
</Route>
错误是:
Warning: You should not use <Route component>
and <Route children>
in the same route; <Route children>
will be ignored
在那种情况下,正确的实施方式是什么?
它出现在 react-router
(v4) 文档中,建议类似于
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
但是将一堆路由组合在一起是否可以实现这一点?
经过一番研究,我想到了这个:
import React, {PropTypes} from "react"
import {Route} from "react-router-dom"
export default class AuthenticatedRoute extends React.Component {
render() {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <Route {...this.props} />
}
}
AuthenticatedRoute.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
component: PropTypes.element,
redirectToLogin: PropTypes.func.isRequired
}
在 render()
中发送动作是否正确?感觉不对。 componentDidMount
或其他一些钩子似乎也不正确。
您将要使用 Redirect
组件。有几种不同的方法可以解决这个问题。这是我喜欢的一个,有一个 PrivateRoute 组件,它接收一个 authed
道具,然后根据该道具进行渲染。
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
现在您的 Route
可以看起来像这样
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
如果您仍然感到困惑,我写的这篇 post 可能会对您有所帮助 -
使用 React Router v4
保护路由和身份验证
看来你犹豫的是创建自己的组件,然后在 render 方法中调度?好吧,您可以通过使用 <Route>
组件的 render
方法来避免这两种情况。除非您真的想要,否则无需创建 <AuthenticatedRoute>
组件。它可以像下面一样简单。请注意 {...routeProps}
传播,确保您继续将 <Route>
组件的属性向下发送到子组件(在本例中为 <MyComponent>
)。
<Route path='/someprivatepath' render={routeProps => {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <MyComponent {...routeProps} anotherProp={somevalue} />
} />
见React Router V4 render documentation
如果您确实想创建专用组件,那么看起来您走对了路。由于 React Router V4 是 纯声明式路由 (它在描述中是这样说的)我认为您不会将重定向代码放在正常组件生命周期之外。查看 code for React Router itself,他们根据是否是服务器端渲染在 componentWillMount
或 componentDidMount
中执行重定向。这是下面的代码,它非常简单,可以帮助您更轻松地确定将重定向逻辑放在何处。
import React, { PropTypes } from 'react'
/**
* The public API for updating the location programatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
static defaultProps = {
push: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
}
isStatic() {
return this.context.router && this.context.router.staticContext
}
componentWillMount() {
if (this.isStatic())
this.perform()
}
componentDidMount() {
if (!this.isStatic())
this.perform()
}
perform() {
const { history } = this.context.router
const { push, to } = this.props
if (push) {
history.push(to)
} else {
history.replace(to)
}
}
render() {
return null
}
}
export default Redirect
Tnx Tyler McGinnis 寻求解决方案。
我的想法来自 Tyler McGinnis 的想法。
const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
return (
<Route
{...rest}
render={
decisionFunc()
? trueComponent
: falseComponent
}
/>
)
}
你可以这样实现
<DecisionRoute path="/signin" exact={true}
trueComponent={redirectStart}
falseComponent={SignInPage}
decisionFunc={isAuth}
/>
decisionFunc 只是一个 return 真或假
的函数
const redirectStart = props => <Redirect to="/orders" />
安装 react-router-dom
然后创建两个组件,一个用于有效用户,另一个用于无效用户。
在 app.js
上试试这个
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';
import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;
class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path="/" render={() =>(
loggedin ? ( <Route component={ValidUser} />)
: (<Route component={InValidUser} />)
)} />
</div>
</Router>
)
}
}
export default App;
我知道这已经有一段时间了,但我一直在为私人和 public 路线开发 npm package。
创建私有路由的方法如下:
<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>
您还可以制作 Public 只有未经授权的用户才能访问的路由
<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>
希望对您有所帮助!
我使用-
实现
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
authenticate props 将传递给组件,例如可以更改用户状态的注册。完成 AppRoutes-
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';
import { config } from '../utils/Config';
export default class AppRoutes extends React.Component {
constructor(props) {
super(props);
// initially assuming that user is logged out
let user = {
isLoggedIn: false
}
// if user is logged in, his details can be found from local storage
try {
let userJsonString = localStorage.getItem(config.localStorageKey);
if (userJsonString) {
user = JSON.parse(userJsonString);
}
} catch (exception) {
}
// updating the state
this.state = {
user: user
};
this.authenticate = this.authenticate.bind(this);
}
// this function is called on login/logout
authenticate(user) {
this.setState({
user: user
});
// updating user's details
localStorage.setItem(config.localStorageKey, JSON.stringify(user));
}
render() {
return (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
<Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
</Switch>
);
}
}
在这里查看完整的项目:https://github.com/varunon9/hello-react
(使用Redux进行状态管理)
如果用户尝试访问任何 url,首先我将检查访问令牌是否可用,如果没有重定向到登录页面,
一旦用户使用登录页面登录,我们就会将其存储在本地存储以及我们的 redux 状态中。 (localstorage 或 cookies..我们暂时不考虑这个话题)。
由于 redux 状态已更新,因此将重新呈现 privateroutes。现在我们有了访问令牌,所以我们要重定向到主页。
将解码后的授权有效负载数据也存储在 redux 状态中,并将其传递给 React 上下文。 (我们不必使用上下文,但要在我们的任何嵌套子组件中访问授权,它可以很容易地从上下文访问,而不是将每个子组件连接到 redux)..
所有不需要特殊角色的路由都可以在登录后直接访问..如果需要像admin这样的角色(我们做了一个受保护的路由,如果没有重定向到未授权的组件,检查他是否有想要的角色)
如果您必须禁用按钮或基于角色的某些东西,则在您的任何组件中类似。
简单的你可以这样做
const authorization = useContext(AuthContext);
const [hasAdminRole] = checkAuth({authorization, roleType:"admin"});
const [hasLeadRole] = checkAuth({authorization, roleType:"lead"});
<Button disable={!hasAdminRole} />Admin can access</Button>
<Button disable={!hasLeadRole || !hasAdminRole} />admin or lead can access</Button>
如果用户尝试在本地存储中插入虚拟令牌怎么办。因为我们确实有访问令牌,所以我们将重定向到主页组件。我的主页组件将进行休息调用以获取数据,因为 jwt 令牌是虚拟的,休息调用将 return 未经授权的用户。所以我确实调用了注销(这将清除本地存储并再次重定向到登录页面)。
如果主页有静态数据并且没有进行任何 api 调用(那么你应该在后端调用 token-verify api 以便你可以在加载主页之前检查 token 是否真实)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import history from './utils/history';
import Store from './statemanagement/store/configureStore';
import Privateroutes from './Privateroutes';
import Logout from './components/auth/Logout';
ReactDOM.render(
<Store>
<Router history={history}>
<Switch>
<Route path="/logout" exact component={Logout} />
<Route path="/" exact component={Privateroutes} />
<Route path="/:someParam" component={Privateroutes} />
</Switch>
</Router>
</Store>,
document.querySelector('#root')
);
History.js
import { createBrowserHistory as history } from 'history';
export default history({});
Privateroutes.js
import React, { Fragment, useContext } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { AuthContext, checkAuth } from './checkAuth';
import App from './components/App';
import Home from './components/home';
import Admin from './components/admin';
import Login from './components/auth/Login';
import Unauthorized from './components/Unauthorized ';
import Notfound from './components/404';
const ProtectedRoute = ({ component: Component, roleType, ...rest })=> {
const authorization = useContext(AuthContext);
const [hasRequiredRole] = checkAuth({authorization, roleType});
return (
<Route
{...rest}
render={props => hasRequiredRole ?
<Component {...props} /> :
<Unauthorized {...props} /> }
/>)};
const Privateroutes = props => {
const { accessToken, authorization } = props.authData;
if (accessToken) {
return (
<Fragment>
<AuthContext.Provider value={authorization}>
<App>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" render={() => <Redirect to="/" />} />
<Route exact path="/home" component={Home} />
<ProtectedRoute
exact
path="/admin"
component={Admin}
roleType="admin"
/>
<Route path="/404" component={Notfound} />
<Route path="*" render={() => <Redirect to="/404" />} />
</Switch>
</App>
</AuthContext.Provider>
</Fragment>
);
} else {
return (
<Fragment>
<Route exact path="/login" component={Login} />
<Route exact path="*" render={() => <Redirect to="/login" />} />
</Fragment>
);
}
};
// my user reducer sample
// const accessToken = localStorage.getItem('token')
// ? JSON.parse(localStorage.getItem('token')).accessToken
// : false;
// const initialState = {
// accessToken: accessToken ? accessToken : null,
// authorization: accessToken
// ? jwtDecode(JSON.parse(localStorage.getItem('token')).accessToken)
// .authorization
// : null
// };
// export default function(state = initialState, action) {
// switch (action.type) {
// case actionTypes.FETCH_LOGIN_SUCCESS:
// let token = {
// accessToken: action.payload.token
// };
// localStorage.setItem('token', JSON.stringify(token))
// return {
// ...state,
// accessToken: action.payload.token,
// authorization: jwtDecode(action.payload.token).authorization
// };
// default:
// return state;
// }
// }
const mapStateToProps = state => {
const { authData } = state.user;
return {
authData: authData
};
};
export default connect(mapStateToProps)(Privateroutes);
checkAuth.js
import React from 'react';
export const AuthContext = React.createContext();
export const checkAuth = ({ authorization, roleType }) => {
let hasRequiredRole = false;
if (authorization.roles ) {
let roles = authorization.roles.map(item =>
item.toLowerCase()
);
hasRequiredRole = roles.includes(roleType);
}
return [hasRequiredRole];
};
解码后的 JWT 令牌样本
{
"authorization": {
"roles": [
"admin",
"operator"
]
},
"exp": 1591733170,
"user_id": 1,
"orig_iat": 1591646770,
"email": "hemanthvrm@Whosebug",
"username": "hemanthvrm"
}
基于 @Tyler McGinnis 的回答。我使用 ES6 语法 和 嵌套路由 和包装组件采用了不同的方法:
import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'
const PrivateRoute = ({ children, authed, ...rest }) =>
<Route
{...rest}
render={(props) => authed ?
<div>
{Children.map(children, child => cloneElement(child, { ...child.props }))}
</div>
:
<Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
export default PrivateRoute
并使用它:
<BrowserRouter>
<div>
<PrivateRoute path='/home' authed={auth}>
<Navigation>
<Route component={Home} path="/home" />
</Navigation>
</PrivateRoute>
<Route exact path='/' component={PublicHomePage} />
</div>
</BrowserRouter>
const Root = ({ session }) => {
const isLoggedIn = session && session.getCurrentUser
return (
<Router>
{!isLoggedIn ? (
<Switch>
<Route path="/signin" component={<Signin />} />
<Redirect to="/signin" />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/something-else" component={SomethingElse} />
<Redirect to="/" />
</Switch>
)}
</Router>
)
}
我之前的回答是不可扩展的。这是我认为好的方法-
您的路线-
<Switch>
<Route
exact path="/"
component={matchStateToProps(InitialAppState, {
routeOpen: true // no auth is needed to access this route
})} />
<Route
exact path="/profile"
component={matchStateToProps(Profile, {
routeOpen: false // can set it false or just omit this key
})} />
<Route
exact path="/login"
component={matchStateToProps(Login, {
routeOpen: true
})} />
<Route
exact path="/forgot-password"
component={matchStateToProps(ForgotPassword, {
routeOpen: true
})} />
<Route
exact path="/dashboard"
component={matchStateToProps(DashBoard)} />
</Switch>
想法是在 component
道具中使用包装器,如果不需要身份验证或已经通过身份验证,它将 return 原始组件,否则将 return 默认组件,例如登录。
const matchStateToProps = function(Component, defaultProps) {
return (props) => {
let authRequired = true;
if (defaultProps && defaultProps.routeOpen) {
authRequired = false;
}
if (authRequired) {
// check if loginState key exists in localStorage (Your auth logic goes here)
if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
return <Component { ...defaultProps } />; // authenticated, good to go
} else {
return <InitialAppState { ...defaultProps } />; // not authenticated
}
}
return <Component { ...defaultProps } />; // no auth is required
};
};
这是简单干净的受保护路线
const ProtectedRoute
= ({ isAllowed, ...props }) =>
isAllowed
? <Route {...props}/>
: <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=>
<Switch>
<Route exact path="/authentificate" component={Login}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/secrets"
component={Secrets}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/polices"
component={Polices}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/grants" component={Grants}/>
<Redirect from="/" to={lastTab}/>
</Switch>
isTokenVerified
是一种检查授权令牌的方法调用,基本上它 returns 布尔值。
我也在寻找答案。这里所有的答案都很好,但是其中 none 给出了如果用户在打开应用程序后启动应用程序时我们如何使用它的答案。 (我的意思是说一起使用cookie)。
无需创建甚至不同的 privateRoute 组件。下面是我的代码
import React, { Component } from 'react';
import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './stores';
import requireAuth from './components/authentication/authComponent'
import SearchComponent from './components/search/searchComponent'
import LoginComponent from './components/login/loginComponent'
import ExampleContainer from './containers/ExampleContainer'
class App extends Component {
state = {
auth: true
}
componentDidMount() {
if ( ! Cookies.get('auth')) {
this.setState({auth:false });
}
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route exact path="/searchComponent" component={requireAuth(SearchComponent)} />
<Route exact path="/login" component={LoginComponent} />
<Route exact path="/" component={requireAuth(ExampleContainer)} />
{!this.state.auth && <Redirect push to="/login"/> }
</Switch>
</BrowserRouter>
</Provider>);
}
}
}
export default App;
这里是 authComponent
import React from 'react';
import { withRouter } from 'react-router';
import * as Cookie from "js-cookie";
export default function requireAuth(Component) {
class AuthenticatedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: Cookie.get('auth')
}
}
componentDidMount() {
this.checkAuth();
}
checkAuth() {
const location = this.props.location;
const redirect = location.pathname + location.search;
if ( ! Cookie.get('auth')) {
this.props.history.push(`/login?redirect=${redirect}`);
}
}
render() {
return Cookie.get('auth')
? <Component { ...this.props } />
: null;
}
}
return withRouter(AuthenticatedComponent)
}
下面我写了博客,你也可以在那里得到更深入的解释。
这是我用 React 和 Typescript 解决它的方法。希望对您有所帮助!
import * as React from 'react';
import { FC } from 'react';
import { Route, RouteComponentProps, RouteProps, Redirect } from 'react-router';
const PrivateRoute: FC<RouteProps> = ({ component: Component, ...rest }) => {
if (!Component) {
return null;
}
const isLoggedIn = true; // Add your provider here
return (
<Route
{...rest}
render={(props: RouteComponentProps<{}>) => isLoggedIn ? (<Component {...props} />) : (<Redirect to={{ pathname: '/', state: { from: props.location } }} />)}
/>
);
};
export default PrivateRoute;
<PrivateRoute component={SignIn} path="/signin" />
接受的答案很好,但它没有解决问题当我们需要我们的组件来反映URL[=30]中的变化时=].
比如说,你的组件代码是这样的:
export const Customer = (props) => {
const history = useHistory();
...
}
然后你改变 URL:
const handleGoToPrev = () => {
history.push(`/app/customer/${prevId}`);
}
组件不会重新加载!
更好的解决方案:
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import store from '../store/store';
export const PrivateRoute = ({ component: Component, ...rest }) => {
let isLoggedIn = !!store.getState().data.user;
return (
<Route {...rest} render={props => isLoggedIn
? (
<Component key={props.match.params.id || 'empty'} {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
} />
)
}
用法:
<PrivateRoute exact path="/app/customer/:id" component={Customer} />
这只是初学者的基本方法,不适用于专业的 redux 开发人员
import React, { useState, useEffect } from "react";
import {
Route,
BrowserRouter as Router,
Switch,
Redirect,
} from "react-router-dom";
import Home from "./components/Home";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
function App() {
const [isAuth, setAuth] = useState(false);
const checkAuth = () => {
// Your auth logic here
setAuth(true);
};
useEffect(() => {
checkAuth();
});
return (
<Router>
<Switch>
<Route
path="/user/dashboard"
render={(props) =>
isAuth ? <Dashboard {...props} /> : <Redirect to="/" />
}
/>
<Route path="/login" component={Login} />
<Route path="/" component={Home} />
</Switch>
</Router>
);
}
所有答案都已过时
在 2021 年,Route
组件的 render
道具用于遗留用途 according to the react-router-dom documentation,在我的情况下它甚至没有工作(我使用的是 react-router- dom5.2.0).
这个可以代替:
import React, { FC } from "react";
import { Route } from "react-router-dom";
const RouteRequiresLogin: FC<React.ComponentProps<typeof Route>> = props => {
const userIsLogged = useLoginStatus();
return (
<Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
);
};
export default RouteRequiresLogin;
用法:
/* A route that requires login */
<RouteRequiresLogin path="/dashboard">
<DashboardPage />
</RouteRequiresLogin>
/* A route that doesn't require login */
<Route path="/sign-up">
<SignUpPage />
</Route>
这是我自己的做法
const RedirectionUnit = () => {
const [user] = useContext(AuthContext);
const pathname = useLocation().pathname;
let redirectTo;
if (user === null) redirectTo = "login";
else if (pathname === "/")
if (user.type === "supervisor" ) redirectTo = "all-parteners";
else if (user.type === "manager" ) redirectTo = "all-employees";
else if (user.type === "employee" ) redirectTo = "unfinished-tasks";
if (redirectTo && '/' + redirectTo !== pathname)
return <Redirect to={redirectTo} />;
return null;
};
const NavigationRoutes = () => {
return (
<>
<Route component={RedirectionUnit} />
{/* prettier-ignore */}
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
<Route exact path="/new-parteners" component={NewParteners} />
<Route exact path="/all-parteners" component={AllParteners} />
<Route exact path="/new-employees" component={NewEmployees} />
<Route exact path="/all-employees" component={AllEmployees} />
<Route exact path="/unfinished-tasks" component={UnfinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route component={NotFound} />
</Switch>
</>
);
};
我喜欢@fermmm 的回答,但在他的实现中,如果用户未登录,呈现的组件将与 url 不匹配。因此它可能会让访问者感到困惑。
所以,而不是
return (
<Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
);
我建议使用:
return (
<Route {...props}>
{userIsLogged ? (
props.children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location },
}}
/>
)}
</Route>
);
在这种情况下,您仍然会渲染组件,但 URL 中的“/login”而不是之前的路由段。
我一直在寻找一种解决方案,其中我的主路由器文件包含验证路由所需的一切。没有嵌套组件需要或复杂如果其他的。以下是我的做法
import React from "react";
import { Routes, Route } from "react-router-dom";
import { Navigate } from "react-router-dom";
// Other imports
export default function AppRoutes() {
// This coming from react-redux
// After a user is logged in this will set in the global state
const { currentUser } = useCurrentUser();
const landing = <Landing />
const authenticate = (component) => {
return currentUser ? component : <Navigate to="/" />;
}
return (
<Routes>
<Route path="/" element={currentUser ? <Home /> : landing} />
<Route path="/blogs/:id" element={authenticate(<Blog />)} />
<Route path="/blogs/:id/edit" element={authenticate(<BlogEdit />)} />
<Route path="/profile" element={authenticate(<Profile />)} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
);
}
我试图实现经过身份验证的路由,但发现 React Router 4 现在阻止了它的工作:
<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
<Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
<Route exact path="/domains" component={DomainsIndex} />
</Route>
错误是:
Warning: You should not use
<Route component>
and<Route children>
in the same route;<Route children>
will be ignored
在那种情况下,正确的实施方式是什么?
它出现在 react-router
(v4) 文档中,建议类似于
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
但是将一堆路由组合在一起是否可以实现这一点?
经过一番研究,我想到了这个:
import React, {PropTypes} from "react"
import {Route} from "react-router-dom"
export default class AuthenticatedRoute extends React.Component {
render() {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <Route {...this.props} />
}
}
AuthenticatedRoute.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
component: PropTypes.element,
redirectToLogin: PropTypes.func.isRequired
}
在 render()
中发送动作是否正确?感觉不对。 componentDidMount
或其他一些钩子似乎也不正确。
您将要使用 Redirect
组件。有几种不同的方法可以解决这个问题。这是我喜欢的一个,有一个 PrivateRoute 组件,它接收一个 authed
道具,然后根据该道具进行渲染。
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
现在您的 Route
可以看起来像这样
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
如果您仍然感到困惑,我写的这篇 post 可能会对您有所帮助 - 使用 React Router v4
保护路由和身份验证看来你犹豫的是创建自己的组件,然后在 render 方法中调度?好吧,您可以通过使用 <Route>
组件的 render
方法来避免这两种情况。除非您真的想要,否则无需创建 <AuthenticatedRoute>
组件。它可以像下面一样简单。请注意 {...routeProps}
传播,确保您继续将 <Route>
组件的属性向下发送到子组件(在本例中为 <MyComponent>
)。
<Route path='/someprivatepath' render={routeProps => {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <MyComponent {...routeProps} anotherProp={somevalue} />
} />
见React Router V4 render documentation
如果您确实想创建专用组件,那么看起来您走对了路。由于 React Router V4 是 纯声明式路由 (它在描述中是这样说的)我认为您不会将重定向代码放在正常组件生命周期之外。查看 code for React Router itself,他们根据是否是服务器端渲染在 componentWillMount
或 componentDidMount
中执行重定向。这是下面的代码,它非常简单,可以帮助您更轻松地确定将重定向逻辑放在何处。
import React, { PropTypes } from 'react'
/**
* The public API for updating the location programatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
static defaultProps = {
push: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
}
isStatic() {
return this.context.router && this.context.router.staticContext
}
componentWillMount() {
if (this.isStatic())
this.perform()
}
componentDidMount() {
if (!this.isStatic())
this.perform()
}
perform() {
const { history } = this.context.router
const { push, to } = this.props
if (push) {
history.push(to)
} else {
history.replace(to)
}
}
render() {
return null
}
}
export default Redirect
Tnx Tyler McGinnis 寻求解决方案。 我的想法来自 Tyler McGinnis 的想法。
const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
return (
<Route
{...rest}
render={
decisionFunc()
? trueComponent
: falseComponent
}
/>
)
}
你可以这样实现
<DecisionRoute path="/signin" exact={true}
trueComponent={redirectStart}
falseComponent={SignInPage}
decisionFunc={isAuth}
/>
decisionFunc 只是一个 return 真或假
的函数const redirectStart = props => <Redirect to="/orders" />
安装 react-router-dom
然后创建两个组件,一个用于有效用户,另一个用于无效用户。
在 app.js
上试试这个import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';
import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;
class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path="/" render={() =>(
loggedin ? ( <Route component={ValidUser} />)
: (<Route component={InValidUser} />)
)} />
</div>
</Router>
)
}
}
export default App;
我知道这已经有一段时间了,但我一直在为私人和 public 路线开发 npm package。
创建私有路由的方法如下:
<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>
您还可以制作 Public 只有未经授权的用户才能访问的路由
<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>
希望对您有所帮助!
我使用-
实现<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
authenticate props 将传递给组件,例如可以更改用户状态的注册。完成 AppRoutes-
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';
import { config } from '../utils/Config';
export default class AppRoutes extends React.Component {
constructor(props) {
super(props);
// initially assuming that user is logged out
let user = {
isLoggedIn: false
}
// if user is logged in, his details can be found from local storage
try {
let userJsonString = localStorage.getItem(config.localStorageKey);
if (userJsonString) {
user = JSON.parse(userJsonString);
}
} catch (exception) {
}
// updating the state
this.state = {
user: user
};
this.authenticate = this.authenticate.bind(this);
}
// this function is called on login/logout
authenticate(user) {
this.setState({
user: user
});
// updating user's details
localStorage.setItem(config.localStorageKey, JSON.stringify(user));
}
render() {
return (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
<Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
</Switch>
);
}
}
在这里查看完整的项目:https://github.com/varunon9/hello-react
(使用Redux进行状态管理)
如果用户尝试访问任何 url,首先我将检查访问令牌是否可用,如果没有重定向到登录页面,
一旦用户使用登录页面登录,我们就会将其存储在本地存储以及我们的 redux 状态中。 (localstorage 或 cookies..我们暂时不考虑这个话题)。
由于 redux 状态已更新,因此将重新呈现 privateroutes。现在我们有了访问令牌,所以我们要重定向到主页。
将解码后的授权有效负载数据也存储在 redux 状态中,并将其传递给 React 上下文。 (我们不必使用上下文,但要在我们的任何嵌套子组件中访问授权,它可以很容易地从上下文访问,而不是将每个子组件连接到 redux)..
所有不需要特殊角色的路由都可以在登录后直接访问..如果需要像admin这样的角色(我们做了一个受保护的路由,如果没有重定向到未授权的组件,检查他是否有想要的角色)
如果您必须禁用按钮或基于角色的某些东西,则在您的任何组件中类似。
简单的你可以这样做
const authorization = useContext(AuthContext);
const [hasAdminRole] = checkAuth({authorization, roleType:"admin"});
const [hasLeadRole] = checkAuth({authorization, roleType:"lead"});
<Button disable={!hasAdminRole} />Admin can access</Button>
<Button disable={!hasLeadRole || !hasAdminRole} />admin or lead can access</Button>
如果用户尝试在本地存储中插入虚拟令牌怎么办。因为我们确实有访问令牌,所以我们将重定向到主页组件。我的主页组件将进行休息调用以获取数据,因为 jwt 令牌是虚拟的,休息调用将 return 未经授权的用户。所以我确实调用了注销(这将清除本地存储并再次重定向到登录页面)。 如果主页有静态数据并且没有进行任何 api 调用(那么你应该在后端调用 token-verify api 以便你可以在加载主页之前检查 token 是否真实)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import history from './utils/history';
import Store from './statemanagement/store/configureStore';
import Privateroutes from './Privateroutes';
import Logout from './components/auth/Logout';
ReactDOM.render(
<Store>
<Router history={history}>
<Switch>
<Route path="/logout" exact component={Logout} />
<Route path="/" exact component={Privateroutes} />
<Route path="/:someParam" component={Privateroutes} />
</Switch>
</Router>
</Store>,
document.querySelector('#root')
);
History.js
import { createBrowserHistory as history } from 'history';
export default history({});
Privateroutes.js
import React, { Fragment, useContext } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { AuthContext, checkAuth } from './checkAuth';
import App from './components/App';
import Home from './components/home';
import Admin from './components/admin';
import Login from './components/auth/Login';
import Unauthorized from './components/Unauthorized ';
import Notfound from './components/404';
const ProtectedRoute = ({ component: Component, roleType, ...rest })=> {
const authorization = useContext(AuthContext);
const [hasRequiredRole] = checkAuth({authorization, roleType});
return (
<Route
{...rest}
render={props => hasRequiredRole ?
<Component {...props} /> :
<Unauthorized {...props} /> }
/>)};
const Privateroutes = props => {
const { accessToken, authorization } = props.authData;
if (accessToken) {
return (
<Fragment>
<AuthContext.Provider value={authorization}>
<App>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" render={() => <Redirect to="/" />} />
<Route exact path="/home" component={Home} />
<ProtectedRoute
exact
path="/admin"
component={Admin}
roleType="admin"
/>
<Route path="/404" component={Notfound} />
<Route path="*" render={() => <Redirect to="/404" />} />
</Switch>
</App>
</AuthContext.Provider>
</Fragment>
);
} else {
return (
<Fragment>
<Route exact path="/login" component={Login} />
<Route exact path="*" render={() => <Redirect to="/login" />} />
</Fragment>
);
}
};
// my user reducer sample
// const accessToken = localStorage.getItem('token')
// ? JSON.parse(localStorage.getItem('token')).accessToken
// : false;
// const initialState = {
// accessToken: accessToken ? accessToken : null,
// authorization: accessToken
// ? jwtDecode(JSON.parse(localStorage.getItem('token')).accessToken)
// .authorization
// : null
// };
// export default function(state = initialState, action) {
// switch (action.type) {
// case actionTypes.FETCH_LOGIN_SUCCESS:
// let token = {
// accessToken: action.payload.token
// };
// localStorage.setItem('token', JSON.stringify(token))
// return {
// ...state,
// accessToken: action.payload.token,
// authorization: jwtDecode(action.payload.token).authorization
// };
// default:
// return state;
// }
// }
const mapStateToProps = state => {
const { authData } = state.user;
return {
authData: authData
};
};
export default connect(mapStateToProps)(Privateroutes);
checkAuth.js
import React from 'react';
export const AuthContext = React.createContext();
export const checkAuth = ({ authorization, roleType }) => {
let hasRequiredRole = false;
if (authorization.roles ) {
let roles = authorization.roles.map(item =>
item.toLowerCase()
);
hasRequiredRole = roles.includes(roleType);
}
return [hasRequiredRole];
};
解码后的 JWT 令牌样本
{
"authorization": {
"roles": [
"admin",
"operator"
]
},
"exp": 1591733170,
"user_id": 1,
"orig_iat": 1591646770,
"email": "hemanthvrm@Whosebug",
"username": "hemanthvrm"
}
基于 @Tyler McGinnis 的回答。我使用 ES6 语法 和 嵌套路由 和包装组件采用了不同的方法:
import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'
const PrivateRoute = ({ children, authed, ...rest }) =>
<Route
{...rest}
render={(props) => authed ?
<div>
{Children.map(children, child => cloneElement(child, { ...child.props }))}
</div>
:
<Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
export default PrivateRoute
并使用它:
<BrowserRouter>
<div>
<PrivateRoute path='/home' authed={auth}>
<Navigation>
<Route component={Home} path="/home" />
</Navigation>
</PrivateRoute>
<Route exact path='/' component={PublicHomePage} />
</div>
</BrowserRouter>
const Root = ({ session }) => {
const isLoggedIn = session && session.getCurrentUser
return (
<Router>
{!isLoggedIn ? (
<Switch>
<Route path="/signin" component={<Signin />} />
<Redirect to="/signin" />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/something-else" component={SomethingElse} />
<Redirect to="/" />
</Switch>
)}
</Router>
)
}
我之前的回答是不可扩展的。这是我认为好的方法-
您的路线-
<Switch>
<Route
exact path="/"
component={matchStateToProps(InitialAppState, {
routeOpen: true // no auth is needed to access this route
})} />
<Route
exact path="/profile"
component={matchStateToProps(Profile, {
routeOpen: false // can set it false or just omit this key
})} />
<Route
exact path="/login"
component={matchStateToProps(Login, {
routeOpen: true
})} />
<Route
exact path="/forgot-password"
component={matchStateToProps(ForgotPassword, {
routeOpen: true
})} />
<Route
exact path="/dashboard"
component={matchStateToProps(DashBoard)} />
</Switch>
想法是在 component
道具中使用包装器,如果不需要身份验证或已经通过身份验证,它将 return 原始组件,否则将 return 默认组件,例如登录。
const matchStateToProps = function(Component, defaultProps) {
return (props) => {
let authRequired = true;
if (defaultProps && defaultProps.routeOpen) {
authRequired = false;
}
if (authRequired) {
// check if loginState key exists in localStorage (Your auth logic goes here)
if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
return <Component { ...defaultProps } />; // authenticated, good to go
} else {
return <InitialAppState { ...defaultProps } />; // not authenticated
}
}
return <Component { ...defaultProps } />; // no auth is required
};
};
这是简单干净的受保护路线
const ProtectedRoute
= ({ isAllowed, ...props }) =>
isAllowed
? <Route {...props}/>
: <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=>
<Switch>
<Route exact path="/authentificate" component={Login}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/secrets"
component={Secrets}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/polices"
component={Polices}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/grants" component={Grants}/>
<Redirect from="/" to={lastTab}/>
</Switch>
isTokenVerified
是一种检查授权令牌的方法调用,基本上它 returns 布尔值。
我也在寻找答案。这里所有的答案都很好,但是其中 none 给出了如果用户在打开应用程序后启动应用程序时我们如何使用它的答案。 (我的意思是说一起使用cookie)。
无需创建甚至不同的 privateRoute 组件。下面是我的代码
import React, { Component } from 'react';
import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './stores';
import requireAuth from './components/authentication/authComponent'
import SearchComponent from './components/search/searchComponent'
import LoginComponent from './components/login/loginComponent'
import ExampleContainer from './containers/ExampleContainer'
class App extends Component {
state = {
auth: true
}
componentDidMount() {
if ( ! Cookies.get('auth')) {
this.setState({auth:false });
}
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route exact path="/searchComponent" component={requireAuth(SearchComponent)} />
<Route exact path="/login" component={LoginComponent} />
<Route exact path="/" component={requireAuth(ExampleContainer)} />
{!this.state.auth && <Redirect push to="/login"/> }
</Switch>
</BrowserRouter>
</Provider>);
}
}
}
export default App;
这里是 authComponent
import React from 'react';
import { withRouter } from 'react-router';
import * as Cookie from "js-cookie";
export default function requireAuth(Component) {
class AuthenticatedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: Cookie.get('auth')
}
}
componentDidMount() {
this.checkAuth();
}
checkAuth() {
const location = this.props.location;
const redirect = location.pathname + location.search;
if ( ! Cookie.get('auth')) {
this.props.history.push(`/login?redirect=${redirect}`);
}
}
render() {
return Cookie.get('auth')
? <Component { ...this.props } />
: null;
}
}
return withRouter(AuthenticatedComponent)
}
下面我写了博客,你也可以在那里得到更深入的解释。
这是我用 React 和 Typescript 解决它的方法。希望对您有所帮助!
import * as React from 'react';
import { FC } from 'react';
import { Route, RouteComponentProps, RouteProps, Redirect } from 'react-router';
const PrivateRoute: FC<RouteProps> = ({ component: Component, ...rest }) => {
if (!Component) {
return null;
}
const isLoggedIn = true; // Add your provider here
return (
<Route
{...rest}
render={(props: RouteComponentProps<{}>) => isLoggedIn ? (<Component {...props} />) : (<Redirect to={{ pathname: '/', state: { from: props.location } }} />)}
/>
);
};
export default PrivateRoute;
<PrivateRoute component={SignIn} path="/signin" />
接受的答案很好,但它没有解决问题当我们需要我们的组件来反映URL[=30]中的变化时=].
比如说,你的组件代码是这样的:
export const Customer = (props) => {
const history = useHistory();
...
}
然后你改变 URL:
const handleGoToPrev = () => {
history.push(`/app/customer/${prevId}`);
}
组件不会重新加载!
更好的解决方案:
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import store from '../store/store';
export const PrivateRoute = ({ component: Component, ...rest }) => {
let isLoggedIn = !!store.getState().data.user;
return (
<Route {...rest} render={props => isLoggedIn
? (
<Component key={props.match.params.id || 'empty'} {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
} />
)
}
用法:
<PrivateRoute exact path="/app/customer/:id" component={Customer} />
这只是初学者的基本方法,不适用于专业的 redux 开发人员
import React, { useState, useEffect } from "react";
import {
Route,
BrowserRouter as Router,
Switch,
Redirect,
} from "react-router-dom";
import Home from "./components/Home";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
function App() {
const [isAuth, setAuth] = useState(false);
const checkAuth = () => {
// Your auth logic here
setAuth(true);
};
useEffect(() => {
checkAuth();
});
return (
<Router>
<Switch>
<Route
path="/user/dashboard"
render={(props) =>
isAuth ? <Dashboard {...props} /> : <Redirect to="/" />
}
/>
<Route path="/login" component={Login} />
<Route path="/" component={Home} />
</Switch>
</Router>
);
}
所有答案都已过时
在 2021 年,Route
组件的 render
道具用于遗留用途 according to the react-router-dom documentation,在我的情况下它甚至没有工作(我使用的是 react-router- dom5.2.0).
这个可以代替:
import React, { FC } from "react";
import { Route } from "react-router-dom";
const RouteRequiresLogin: FC<React.ComponentProps<typeof Route>> = props => {
const userIsLogged = useLoginStatus();
return (
<Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
);
};
export default RouteRequiresLogin;
用法:
/* A route that requires login */
<RouteRequiresLogin path="/dashboard">
<DashboardPage />
</RouteRequiresLogin>
/* A route that doesn't require login */
<Route path="/sign-up">
<SignUpPage />
</Route>
这是我自己的做法
const RedirectionUnit = () => {
const [user] = useContext(AuthContext);
const pathname = useLocation().pathname;
let redirectTo;
if (user === null) redirectTo = "login";
else if (pathname === "/")
if (user.type === "supervisor" ) redirectTo = "all-parteners";
else if (user.type === "manager" ) redirectTo = "all-employees";
else if (user.type === "employee" ) redirectTo = "unfinished-tasks";
if (redirectTo && '/' + redirectTo !== pathname)
return <Redirect to={redirectTo} />;
return null;
};
const NavigationRoutes = () => {
return (
<>
<Route component={RedirectionUnit} />
{/* prettier-ignore */}
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
<Route exact path="/new-parteners" component={NewParteners} />
<Route exact path="/all-parteners" component={AllParteners} />
<Route exact path="/new-employees" component={NewEmployees} />
<Route exact path="/all-employees" component={AllEmployees} />
<Route exact path="/unfinished-tasks" component={UnfinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route component={NotFound} />
</Switch>
</>
);
};
我喜欢@fermmm 的回答,但在他的实现中,如果用户未登录,呈现的组件将与 url 不匹配。因此它可能会让访问者感到困惑。
所以,而不是
return (
<Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
);
我建议使用:
return (
<Route {...props}>
{userIsLogged ? (
props.children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location },
}}
/>
)}
</Route>
);
在这种情况下,您仍然会渲染组件,但 URL 中的“/login”而不是之前的路由段。
我一直在寻找一种解决方案,其中我的主路由器文件包含验证路由所需的一切。没有嵌套组件需要或复杂如果其他的。以下是我的做法
import React from "react";
import { Routes, Route } from "react-router-dom";
import { Navigate } from "react-router-dom";
// Other imports
export default function AppRoutes() {
// This coming from react-redux
// After a user is logged in this will set in the global state
const { currentUser } = useCurrentUser();
const landing = <Landing />
const authenticate = (component) => {
return currentUser ? component : <Navigate to="/" />;
}
return (
<Routes>
<Route path="/" element={currentUser ? <Home /> : landing} />
<Route path="/blogs/:id" element={authenticate(<Blog />)} />
<Route path="/blogs/:id/edit" element={authenticate(<BlogEdit />)} />
<Route path="/profile" element={authenticate(<Profile />)} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
);
}