2016-08-23 239 views
19

我使用Reux和React Router與Redux製作網站。途徑(頁)很多需要登錄我也可以把登錄,如果用戶沒有登錄在這樣:在React路由器上,如何保持登錄狀態甚至刷新頁面?

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鉤重定向到/登錄「如果用戶路徑沒有登錄。用於檢查用戶登錄的數據在商店中,並且在用戶登錄後將被更新。

它的工作完美,但問題是當我刷新頁面時,存儲將被重置,用戶不會登錄狀態返回。

我知道發生這種情況是因爲Redux存儲區只是內存存儲,所以refesh頁面會丟失所有數據。

檢查每個刷新的服務器會話可以工作,但這可能是太多的請求,所以看起來不好的主意。

將登錄狀態數據保存到localStorage可能是有用的,但在這種情況下,我應該檢查每個AJAX調用失敗,因爲會話過期或者不存在,因爲會話已經過期或者不存在。

有沒有辦法更清楚地解決這個問題?我的網站會使用很多人,所以我想盡可能減少XHR呼叫。

任何意見將不勝感激。

回答

24

另一種方法是使用每個路由所需的JSON Web Tokens (JWT)localStorage來檢查JWT。

TL; DR

  • 在前端您有根據服務器上的認證查詢您的 服務器的JWT一個登入和註冊的路線。一旦 通過適當的JWT,您將設置一個狀態屬性爲 true。您可以使用註銷路線,以允許用戶將此 狀態設置爲false。

  • 包含您的路由的index.js可以在渲染前檢查本地存儲 ,從而消除刷新時丟失狀態 的問題,但保持一定的安全性。

  • 在你的應用需要認證的所有路由都通過組合組件呈現 ,並用具有JWTs 的在服務器上的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爲真,則組件在此示例中爲GraphIsAuthenticated。否則默認回到根路由。


然後你可以像這樣構建一個組合組件,通過它來渲染你所有的路由。它將在渲染之前檢查你所持有的用戶是否被認證(布爾)的狀態。

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,並建立通過行動的創建者,以驗證狀態的動作 - >終極版店。此示例makes use of 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!')); 
     }); 
    }; 
} 

您還需要設置你的店(終極版在這種情況下),當然還有行動的創建者。

'真正'的安全來自後端。爲此,您使用localStorage將JWT保留在前端,並將其傳遞到具有敏感/受保護信息的任何API調用。

在服務器API上爲用戶創建並解析JWT是另一步。 I have found passport to be effective.

+0

在你的例子中,IsAuthenticated是ComposedComponent,是否正確? – klode

+0

'RequiredAuth'是組成組件,而'IsAuthenticated'可以是任何數量的需要'state.authenticated'爲true的命名組件。更新了答案。 – alexi2

+0

謝謝,新編輯更清晰。 – klode

1

爲什麼不在登錄狀態和過期日期使用sessionStorage?你將不得不編寫更多的代碼來檢查sessionStorage狀態,但這是我認爲你可以保存發送XHR調用的唯一方法。

相關問題