首页 文章

在React Router上,如何保持登录状态甚至刷新页面?

提问于
浏览
28

我正在使用React和React Router与Redux Build 网站 . 许多路由(页面)需要登录 . 如果用户未登录,我可以重定向到登录:

function requireAuth(nextState, replace) {
    let loggedIn = store.getState().AppReducer.UserReducer.loggedIn;

    if(!loggedIn) {
        replace({
            pathname: '/login',
            state: {
                nextpathname: nextState.location.pathname
            }
        });
    }
}

ReactDOM.render(
    <Provider store={store}>
        <Router history={history}>
            <Route path="/" component={App}>
                <IndexRoute component={Index} />
                <Route path="login" component={Login} />
                <Route path="register" component={Register} />
                <Route path="dashboard" component={Graph} onEnter={requireAuth}>
                    ... some other route requires logged in ...
                </Route>
            </Route>
        </Router>
    </Provider>,
    document.getElementById('entry')
);

如果用户没有登录,请查看代码,我使用onEnter挂钩重定向到'/ login'路由 . 用于检查用户登录的数据是在商店中,并且在用户登录后它将更新 .

它工作得很好,但问题是当我刷新页面时,存储将被重置并且用户没有登录状态 .

我知道这是因为Redux存储只是内存存储,所以refesh页面会丢失所有数据 .

检查服务器会话每次刷新都可以工作,但这可能是太多的请求,所以这看起来不错 .

将登录状态数据保存到localStorage可能有效,但在这种情况下,我应该检查每个AJAX调用失败该请求被拒绝,因为会话已过期或不存在像某些东西,这看起来也是个坏主意 .

有没有办法更清楚地解决这个问题?我的网站将会使用很多人,所以我希望尽可能减少XHR呼叫 .

任何建议将非常感谢 .

2 回答

  • 1

    另一种方法是使用每个路由所需的JSON Web Tokens (JWT),并使用localStorage来检查JWT .

    TL;DR

    • 在前端,您有一个登录和注册路由,根据服务器上的身份验证,在服务器上查询JWT . 一旦通过适当的JWT,您就可以将state的属性设置为true . 您可以使用注销路由,允许用户将此状态设置为false .

    • 包含您的路由的index.js可以在呈现之前检查本地存储,从而消除了在刷新时丢失状态但保持一定安全性的问题 .

    • 在您的应用程序中需要身份验证的所有路由都是通过组合组件呈现的,并且必须在标头中包含JWT以便在服务器API上进行授权 .

    设置它需要一点时间,但它会使您的应用程序“合理”安全 .


    To solve your problem:

    检查 index.js 文件中路由之前的本地存储,如下所示,如果需要,将状态更新为已验证状态 .

    该应用程序通过JWT保护API以保护安全性,这将解决您的刷新问题,并维护与服务器和数据的安全链接 .

    因此在路线中你会有这样的东西:

    index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    import { createStore, applyMiddleware, compose } from 'redux';
    import { Router, Route, browserHistory, IndexRoute } from 'react-router';
    import reduxThunk from 'redux-thunk';
    import { AUTHENTICATE_THE_USER } from './actions/types';
    import RequireAuth from './components/auth/require_auth';
    import reducers from './reducers';
    
    /* ...import necessary components */
    
    const createStoreWithMiddleware = compose(applyMiddleware(reduxThunk))(createStore);
    
    const store = createStoreWithMiddleware(reducers);
    
    /* ... */
    
    // Check for token and update application state if required
    const token = localStorage.getItem('token');
    if (token) {
        store.dispatch({ type: AUTHENTICATE_THE_USER });
    }
    
    /* ... */
    
    ReactDOM.render(
      <Provider store={store}>
        <Router history={history}>
          <Route path="/" component={App}>
            <IndexRoute component={Index} />
            <Route path="login" component={Login} />
            <Route path="register" component={Register} />
            <Route path="dashboard" component={RequireAuth{Graph}} />
            <Route path="isauthenticated" component={RequireAuth(IsAuthenticated)} />
            ... some other route requires logged in ...
          </Route>
        </Router>
      </Provider>
      , .getElementById('entry'));
    

    RequiredAuth 是组合组件,而 GraphIsAuthenticated (可以是任意数量的适当命名的组件)要求 state.authenticated 为真 .

    组件,在这种情况下 GraphIsAuthenticatedstate.authenticated 为真时呈现 . 否则默认返回根路由 .


    然后你可以像这样构建一个Composed Component,通过它来渲染所有的路径 . 在渲染之前,它将检查您持有的状态(无论用户是否经过身份验证(布尔值)) .

    require_auth.js

    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    
    export default function (ComposedComponent) {
    
      // If user not authenticated render out to root
    
      class Authentication extends Component {
        static contextTypes = {
          router: React.PropTypes.object
        };
    
        componentWillMount() {
          if (!this.props.authenticated) {
            this.context.router.push('/');
          }
        }
    
        componentWillUpdate(nextProps) {
          if (!nextProps.authenticated) {
            this.context.router.push('/');
          }
        }
    
        render() {
          return <ComposedComponent {...this.props} />;
        }
      }
    
      function mapStateToProps(state) {
        return { authenticated: state.authenticated };
      }
    
      return connect(mapStateToProps)(Authentication);
    }
    

    在注册/注册方面,您可以创建一个存储JWT的操作,并通过action-creator - > redux store设置要进行身份验证的状态 . 此示例makes use of axios运行异步HTTP请求响应周期 .

    export function signinUser({ email, password }) {
    
      // Note using the npm package 'redux-thunk'
      // giving direct access to the dispatch method
      return function (dispatch) {
    
        // Submit email and password to server
        axios.post(`${API_URL}/signin`, { email, password })
          .then(response => {
            // If request is good update state - user is authenticated
            dispatch({ type: AUTHENTICATE_THE_USER });
    
            // - Save the JWT in localStorage
            localStorage.setItem('token', response.data.token);
    
            // - redirect to the route '/isauthenticated'
            browserHistory.push('/isauthenticated');
          })
          .catch(() => {
            // If request is bad show an error to the user
            dispatch(authenticationError('Incorrect email or password!'));
          });
      };
    }
    

    你还需要设置你的商店(在这种情况下是Redux)和行动创建者 .

    “真正的”安全性来自后端 . 为此,您可以使用localStorage将JWT保留在前端,并将其在标头中传递给任何具有敏感/受保护信息的API调用 .

    在服务器API上为用户创建和解析JWT是另一个步骤 . I have found passport to be effective.

  • 31

    为什么不将sessionStorage与登录状态和到期日期一起使用?您将不得不编写更多代码来检查sessionStorage状态,但这是我认为您可以保存XHR调用的唯一方法 .

相关问题