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

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

问题描述

我正在使用 React、React Router 和 Redux 制作网站.很多路由(页面)都需要用户登录.如果用户没有像这样登录,我可以重定向到登录页面:

function requireAuth(nextState, replace) {让登录 = store.getState().AppReducer.UserReducer.loggedIn;如果(!登录){代替({路径名:'/登录',状态: {下一个路径名:nextState.location.pathname}});}}ReactDOM.render(<提供者商店={商店}><路由器历史={历史}><Route path="/" component={App}><IndexRoute 组件={Index}/><Route path="login" component={Login}/><Route path="register" component={Register}/><Route path="dashboard" component={Graph} onEnter={requireAuth}>...其他一些路线需要登录...</路线></路线></路由器></提供者>,document.getElementById('entry'));

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

它运行良好,但问题是当我刷新页面时,商店被重置并且用户没有重新登录状态.

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

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

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

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

任何建议将不胜感激.

解决方案

另一种方法是使用 JSON Web Tokens (JWT)) 是每条路线所需的,以及 localStoragea> 检查 JWT.

TL;DR

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

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

  • 在您的应用程序中需要身份验证的所有路由都被呈现通过一个组合组件,并在必要时确保标头中有 JWT 用于在服务器 API 上进行授权.

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


解决您的问题:

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

应用程序维护安全性,因为 API 由 JWT 保护,这将解决您的刷新问题,并维护到您的服务器和数据的安全链接.

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

index.js

从'react'导入React;从 'react-dom' 导入 ReactDOM;从'react-redux'导入{提供者};import { createStore, applyMiddleware, compose } from 'redux';从反应路由器"导入{路由器,路由,浏览器历史,索引路由};从redux-thunk"导入 reduxThunk;从 './actions/types' 导入 { AUTHENTICATE_THE_USER };从'./components/auth/require_auth'导入RequireAuth;从'./reducers'导入reducers;/* ...导入必要的组件*/const createStoreWithMiddleware = compose(applyMiddleware(reduxThunk))(createStore);const store = createStoreWithMiddleware(reducers);/* ... *///检查令牌并在需要时更新应用程序状态const token = localStorage.getItem('token');如果(令牌){store.dispatch({ type: AUTHENTICATE_THE_USER });}/* ... */ReactDOM.render(<提供者商店={商店}><路由器历史={历史}><路由路径="/";组件={应用}><IndexRoute 组件={Index}/><路由路径=登录"组件={登录}/><路由路径=注册"组件={注册}/><路线路径=仪表板"组件={RequireAuth(Graph)}/><路由路径=已验证";组件={RequireAuth(IsAuthenticated)}/>...其他一些路线需要登录...</路线></路由器></提供者>, document.getElementById('entry'));

RequiredAuth 是组合组件,而 GraphIsAuthenticated(可以是任意数量的适当命名的组件)需要 状态.验证为真.

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


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

require_auth.js

import React, { Component } from 'react';从'react-redux'导入{连接};导出默认函数(ComposedComponent){//如果用户未通过身份验证呈现给 root类身份验证扩展组件{静态上下文类型 = {路由器:React.PropTypes.object};componentWillMount() {如果(!this.props.authenticated){this.context.router.push('/');}}componentWillUpdate(nextProps) {如果(!nextProps.authenticated){this.context.router.push('/');}}使成为() {返回 <ComposedComponent {...this.props}/>;}}函数 mapStateToProps(state) {返回 { 已认证:state.authenticated };}返回连接(mapStateToProps)(身份验证);}


在注册/登录端,您可以创建一个操作来存储 JWT 并设置状态以通过操作创建者进行身份验证 ->还原商店.这个例子利用axios来运行异步HTTP请求响应周期.

export function signinUser({ email, password }) {//注意使用 npm 包 'redux-thunk'//直接访问调度方法返回函数(调度){//向服务器提交邮箱和密码axios.post(`${API_URL}/signin`, { email, password }).then(响应 => {//如果请求是好的更新状态 - 用户已通过身份验证调度({ 类型:AUTHENTICATE_THE_USER });//- 将 JWT 保存在 localStorage 中localStorage.setItem('token', response.data.token);//- 重定向到路由 '/isauthenticated'browserHistory.push('/已验证');}).catch(() => {//如果请求错误,则向用户显示错误dispatch(authenticationError('电子邮件或密码不正确!'));});};}


当然,您还需要设置您的商店(在本例中为 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>
  , document.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天全站免登陆