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

mod*_*tor 36 javascript local-storage reactjs react-router react-redux

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

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')
);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

任何建议将非常感谢.

ale*_*xi2 39

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

TL; DR

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

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

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

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


要解决您的问题:

检查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'));
Run Code Online (Sandbox Code Playgroud)

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

在这种情况下Graph,IsAuthenticated 如果state.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);
}
Run Code Online (Sandbox Code Playgroud)

在注册/登入侧,你可以创建一个存储JWT,并建立通过行动的创建者,以验证状态的动作- >终极版商店.此示例使用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!'));
      });
  };
} 
Run Code Online (Sandbox Code Playgroud)

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

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

在服务器API上为用户创建和解析JWT是另一个步骤.我发现护照有效.