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

查看:221
本文介绍了在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}>
<路由器历史记录= {历史记录}>
< Route path =/component = {App}>
< IndexRoute component = {Index} />
< Route path =logincomponent = {登录} />
<路径路径=注册组件= {注册} />
<路径路径=仪表板组件= {图表} onEnter = {requireAuth}>
...其他一些路线需要登录...
< / Route>
< / Route>
< / Router>
< / Provider> ,
document.getElementById('entry')
);

请参阅代码,如果用户不是,请使用onEnter hook重定向到'/ login'路由登录。用于检查用户登录的数据在商店中,并且在用户登录后将更新。



它运行正常,但问题是当我刷新页面时,商店将被重置,用户没有登录状态。



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



每次刷新时检查服务器会话都可以正常工作,但这可能是请求太多,所以这看起来不太好。



将登录状态数据保存到localStorage可能会有效,但在这种情况下,我应该检查每个AJAX调用是否因为会话过期或不存在而拒绝该请求,这看起来也不错。



有没有办法更清楚地解决这个问题?我的网站会使用很多人,所以我想尽可能减少XHR电话。



任何建议都将非常感激。

解决方案

另一种方法是使用 JSON Web令牌(JWT)每条路线都需要,并 localStorage 检查JWT。



TL; DR




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


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


  • 所有需要在您的应用程序中进行身份验证的路由都通过组合组件呈现
    ,并且需要在
    中使用JWT用于服务器API授权的标题。




设置此项需要一些时间,但它会合理地使您的应用程序'安全。






解决您的问题:



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



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

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



index.js

  import来自'react'的React; 
从'react-dom'导入ReactDOM;来自'react-redux'的
import {Provider};来自'redux'的
import {createStore,applyMiddleware,compose};来自'react-router'的
import {Router,Route,browserHistory,IndexRoute};
从'redux-thunk'导入reduxThunk;来自'./actions/types'的
import {AUTHENTICATE_THE_USER};
从'./components/auth/require_auth'导入RequireAuth;
从'./reducers'导入减速器;

/ * ...导入必要的组件* /

const createStoreWithMiddleware = compose(applyMiddleware(reduxThunk))(createStore);

const store = createStoreWithMiddleware(redurs);

/ * ... * /

//如果需要,检查令牌和更新应用程序状态
const token = localStorage.getItem('token');
if(token){
store.dispatch({type:AUTHENTICATE_THE_USER});
}

/ * ... * /

ReactDOM.render(
< Provider store = {store}>
<路由器历史记录= {历史记录}>
<路径路径=/组件= {应用程序}>
< IndexRoute组件= {索引} />
<路径path =logincomponent = {Login} />
< Route path =registercomponent = {Register} />
< Route path =dashboardcomponent = {RequireAuth {Graph}} />
< Route path =isauthenticatedcomponent = {RequireAuth(IsAuthenticated)} />
...其他一些路线需要登录...
< / Route>
< / Router>
< / Provider>
,.getElementById('entry'));

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



组件,在本例中为 Graph 如果 state.authenticated 为true,则呈现IsAuthenticated 。否则默认返回到根路径。






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



require_auth.js

  import来自'react'的React,{Component};来自'react-redux'的
import {connect};

导出默认函数(ComposedComponent){

//如果未经过身份验证的用户渲染到根

类身份验证扩展组件{
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} /> ;;
}
}

函数mapStateToProps(state){
return {authenticated:state.authenticated};
}

return connect(mapStateToProps)(身份验证);
}






在注册/登录您可以创建一个存储JWT的动作,并通过动作创建者 - > redux商店设置要进行身份验证的状态。此示例使用axios 来运行异步HTTP请求响应周期。

 导出函数signinUser({email,password}){

//注意使用npm包'redux-thunk'
//直接访问调度方法
返回函数(调度){

//向服务器提交电子邮件和密码
axios.post(`$ {API_URL} / signin`,{email,password})
.then(response => {
//如果请求是良好的更新状态 - 用户通过身份验证
dispatch({type:AUTHENTICATE_THE_USER});

// - 将JWT保存在localStorage
localStorage.setItem('token',response.data.token);

// - 重定向到路由'/ isauthenticated'
browserHistory.push('/ isauthenticated');
})
.catch(()=> {
//如果请求错误则显示错误对我们来说er
dispatch(authenticationError('不正确的电子邮件或密码!'));
});
};
}






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



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



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


I'm making the website with React and React Router with Redux. Lots of routes(pages) requires logged in. I can redirect to login if 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 onEnter hook to redirect to '/login' route if user is not logged in. Data for checking user logged in is in the store, and it will update after user logged in.

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

I know this happens because Redux store is just memory storage, so refesh page will loose all data.

Check the server session on every refresh can be work but this might be too much request, so that looks bad idea.

Save the logged in state data into localStorage might be 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 it looks like bad idea either.

Is there a way to solve this problem more clearly? My website will gonna use lots of people, so I want to reduce XHR calls 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天全站免登陆