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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39097440/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 22:12:09  来源:igfitidea点击:

On React Router, how to stay logged in state even page refresh?

javascriptreactjsreact-reduxreact-routerlocal-storage

提问by modernator

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:

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

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.

请查看代码,如果用户未登录,我使用 onEnter 钩子重定向到“/login”路由。检查用户是否登录的数据在商店中,用户登录后会更新。

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.

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

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.

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

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.

有没有办法更简单地解决这个问题?我的网站需要处理大量用户,所以我想尽可能减少 XHR 调用。

Any advice will be very appreciated.

任何建议将不胜感激。

回答by alexi2

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

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

TL;DR

TL; 博士

  • 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.

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

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

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

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.jsfile as shown below, updating the state to authenticated if required.

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

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.

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

Thus in the routes you would have something like this:

因此,在路线中你会有这样的事情:

index.js

索引.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'));

RequiredAuthis the composed component while Graphand IsAuthenticated(can be any number of appropriately named components) require the state.authenticatedto be true.

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

The Components, in this case Graphand IsAuthenticatedrendered if the state.authenticatedis true. Otherwise is defaults back to the root route.

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



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

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 axiosto run the async HTTP request response cycle.

在注册/登录方面,您可以创建一个操作来存储 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!'));
      });
  };
} 


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

当然,您还需要设置您的商店(在本例中为 Redux)和动作创建者。

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.

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

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

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

回答by aviramtsi

Why not using sessionStorage with the logged in state and expiration date? You will have to write more code for checking the sessionStorage state but that's the only way in my opinion you can save the XHR call from being sent.

为什么不使用具有登录状态和到期日期的 sessionStorage?您将不得不编写更多代码来检查 sessionStorage 状态,但在我看来,这是您可以避免发送 XHR 调用的唯一方法。