在React Router上,即使页面刷新也如何保持登录状态? [英] On React Router, how to stay logged in state even page refresh?

查看:111
本文介绍了在React Router上,即使页面刷新也如何保持登录状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用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')
);

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

它工作正常,但是问题是当我刷新页面时,存储被重置并且用户未重新登录状态.

我知道发生这种情况是因为Redux存储只是内存存储,因此刷新页面将丢失存储中的所有数据.

在每次刷新时检查服务器会话都可以,但是请求可能过多,因此这似乎是个坏主意.

将登录的状态数据保存到localStorage可能可行,但是在这种情况下,我应该检查每个AJAX调用是否失败,因为会话已过期或不存在某种请求而拒绝了该请求,这似乎也是一个坏主意.

有没有一种方法可以更简单地解决这个问题?我的网站需要处理大量用户,因此我想尽可能减少XHR呼叫.

任何建议将不胜感激.

解决方案

另一种可行的方法是使用 JSON Web令牌(JWT) 和每条路由所需的 localStorage 检查JWT.

TL; DR

  • 在前端,您有登录和注册路线,可查询您的 JWT服务器根据服务器上的身份验证进行认证.一次 通过适当的JWT,然后您将state属性设置为 真的.您可以有一个退出路线,允许用户设置此路线 声明为假.

  • 包含路由的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'));

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

如果state.authenticated为true,则呈现组件,在这种情况下为GraphIsAuthenticated.否则默认返回到根路由.


然后,您可以构建一个这样的组合组件,通过它来呈现所有路线.在渲染之前,它将检查您所持有的用户是否已通过身份验证(布尔型)的状态为true.

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并将状态设置为通过动作创建者-> redux存储进行身份验证.此示例利用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'm making a website with React, React Router, and Redux. Lots of routes (pages) require users to be logged in. I can redirect to the login page if the user is not logged in like this:

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')
);

Please see the code, I used the onEnter hook to redirect to the '/login' route if the user is not logged in. Data for checking if the user is logged in is in the store and it will update after the user logs in.

It's working perfectly, but the problem is when I refresh the page, the store is reset and the user is not logged in state back.

I know this happens because the Redux store is just memory storage, so refreshing the page will lose all data from the store.

Checking the server session on every refresh may work but this might be too many requests, so that seems like a bad idea.

Saving the logged in state data to localStorage might work, but in this case, I should check every AJAX calls fail that request rejected because session is expired or not exists like something, and that seems like a bad idea too.

Is there a way to solve this problem more simply? My website needs to handle lots of users so I want to reduce XHR calls as much as possible.

Any advice will be very appreciated.

解决方案

Another way to go is to use JSON Web Tokens (JWT) that are required for each route, and localStorage to check for the JWT.

TL;DR

  • On the front end you have a signin and signup route that queries your server for a JWT according to the authentication on the server. Once passed the appropriate JWT you would then set a property of state to true. You can have a signout route that allows the user to set this state to false.

  • The index.js which contains your routes can check local storage before rendering, thus eliminating your problem with losing the state on refresh but keeping some security.

  • All routes requiring authentication in your application are rendered through a Composed Component, and secured with the necessity of having JWTs in the header for authorization on the server API.

Setting this up takes a little time but it will make your application 'reasonably' secure.


To solve your problem:

Check the local storage before the routes in your index.js file as shown below, updating the state to authenticated if required.

The application maintains security with the fact that the API is secured by the JWT which would solve your refresh issue, and maintain a secure link to your server and data.

Thus in the routes you would have something like this:

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 is the composed component while Graph and IsAuthenticated (can be any number of appropriately named components) require the state.authenticated to be true.

The Components, in this case Graph and IsAuthenticated rendered if the state.authenticated is true. Otherwise is defaults back to the root route.


Then you could build a Composed Component like this, through which all your routes are rendered. It will check that the state in which you are holding whether or not the user is authenticated (a boolean) is true before rendering.

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);
}


On the signup/signin side you could create an action that stores the JWT and sets up the state to authenticated through an action-creator -> redux store. This example makes use of axios to run the async HTTP request response cycle.

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!'));
      });
  };
} 


You would also need to set up your store (Redux in this case) and action creator of course.

The 'real' security comes from the back end. And to do this you use localStorage to keep the JWT on the front end and pass it in the header to any API calls that have sensitive/protected information.

Creating and parsing the JWT for users on the server API is another step. I have found passport to be effective.

这篇关于在React Router上,即使页面刷新也如何保持登录状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆