最近使用React和Redux構建一個後臺項目,在作登陸系統的時候,看了網上不少資料,通常都是使用sessionStorage(包括Cookie,下略)或者localStorage保存從服務器獲取的token,而後使用react-router中onEnter這個方法依據sessionStorage或者localStorage中是否存在相應的token來斷定登陸狀態。react
Cookie, LocalStorage 與 SessionStorage的詳解能夠參考:詳說 Cookie, LocalStorage 與 SessionStorage一文。git
react-router的onEnter方法使用能夠參考react-router官方的用例:auth-flow。
倉庫地址:https://github.com/reactjs/re...github
auth-flow這個用例使用localStorage來保存token,react-router的onEnter調用requireAuth方法來判斷auth.loggedIn()是否能正確返回localStorage.token,來維持登陸狀態,這也是目前經常使用的作法。redux
app.jssegmentfault
import React from 'react' import { render } from 'react-dom' import { browserHistory, Router, Route, Link, withRouter } from 'react-router' import auth from './auth' const App = React.createClass({ getInitialState() { return { loggedIn: auth.loggedIn() } }, updateAuth(loggedIn) { this.setState({ loggedIn: loggedIn }) }, componentWillMount() { auth.onChange = this.updateAuth auth.login() }, render() { return ( <div> <ul> <li> {this.state.loggedIn ? ( <Link to="/logout">Log out</Link> ) : ( <Link to="/login">Sign in</Link> )} </li> <li><Link to="/about">About</Link></li> <li><Link to="/dashboard">Dashboard</Link> (authenticated)</li> </ul> {this.props.children || <p>You are {!this.state.loggedIn && 'not'} logged in.</p>} </div> ) } }) const Dashboard = React.createClass({ render() { const token = auth.getToken() return ( <div> <h1>Dashboard</h1> <p>You made it!</p> <p>{token}</p> </div> ) } }) const Login = withRouter( React.createClass({ getInitialState() { return { error: false } }, handleSubmit(event) { event.preventDefault() const email = this.refs.email.value const pass = this.refs.pass.value auth.login(email, pass, (loggedIn) => { if (!loggedIn) return this.setState({ error: true }) const { location } = this.props if (location.state && location.state.nextPathname) { this.props.router.replace(location.state.nextPathname) } else { this.props.router.replace('/') } }) }, render() { return ( <form onSubmit={this.handleSubmit}> <label><input ref="email" placeholder="email" defaultValue="joe@example.com" /></label> <label><input ref="pass" placeholder="password" /></label> (hint: password1)<br /> <button type="submit">login</button> {this.state.error && ( <p>Bad login information</p> )} </form> ) } }) ) const About = React.createClass({ render() { return <h1>About</h1> } }) const Logout = React.createClass({ componentDidMount() { auth.logout() }, render() { return <p>You are now logged out</p> } }) function requireAuth(nextState, replace) { if (!auth.loggedIn()) { replace({ pathname: '/login', state: { nextPathname: nextState.location.pathname } }) } } render(( <Router history={browserHistory}> <Route path="/" component={App}> <Route path="login" component={Login} /> <Route path="logout" component={Logout} /> <Route path="about" component={About} /> <Route path="dashboard" component={Dashboard} onEnter={requireAuth} /> </Route> </Router> ), document.getElementById('example'))
auth.js瀏覽器
module.exports = { login(email, pass, cb) { cb = arguments[arguments.length - 1] if (localStorage.token) { if (cb) cb(true) this.onChange(true) return } pretendRequest(email, pass, (res) => { if (res.authenticated) { localStorage.token = res.token if (cb) cb(true) this.onChange(true) } else { if (cb) cb(false) this.onChange(false) } }) }, getToken() { return localStorage.token }, logout(cb) { delete localStorage.token if (cb) cb() this.onChange(false) }, loggedIn() { return !!localStorage.token }, onChange() {} } function pretendRequest(email, pass, cb) { setTimeout(() => { if (email === 'joe@example.com' && pass === 'password1') { cb({ authenticated: true, token: Math.random().toString(36).substring(7) }) } else { cb({ authenticated: false }) } }, 0) }
localStorage等本地存儲容器保存一些用戶信息,多少可能會有潛在的風險,那麼可不能夠不使用這些本地存儲來維持用戶狀態呢?服務器
因而我嘗試用redux結合react-router來保持用戶的登陸狀態,最開始的思路是用onEnter調用一個方法來獲取store裏的登陸狀態信息,可是發現react-router的路由聲明中並不能從store中拿到props,只有路由的history等信息。可能水平有限,只能處處翻文檔,無心間在Github中發現一個用例:react-redux-jwt-auth-examplesession
這個用例使用了一個高級函數(high-order function,用例中爲requireAuthentication)來包裝須要登陸權限的Compenent(用例中爲ProtectedView),這個Compenent位於全部須要登陸權限的頂層:react-router
routers.jsapp
import {HomeView, LoginView, ProtectedView, AView, BView } from '../views'; import {requireAuthentication} from '../components/AuthenticatedComponent'; export default( <Route path='/' component={App}> <IndexRoute component={HomeView}/> <Route path="login" component={LoginView}/> <Route path="protected" component={requireAuthentication(ProtectedView)} <Route path="a" component={AView}/> <Route path="b" component={BVieew}/> </Route> </Route> );
利用requireAuthentication()這個高階函數將ProtectedView這個Compenent做爲參數傳人,requireAuthentication()中生成一個Compenent,而後調用react-redux中的connect結合mapStateToProps就能將store中的登陸狀態,token等信息塞入Props中,當前這個requireAuthentication中的Compenent根據Props中的狀態信息來決定是否繼續渲染ProtectedView Compenent,或者在用戶進行頁面跳轉,檢測到登陸狀態爲false時,就會重定向到登陸頁面。
AuthenticatedComponent.js
import React from 'react'; import {connect} from 'react-redux'; import {pushState} from 'redux-router'; export function requireAuthentication(Component) { class AuthenticatedComponent extends React.Component { componentWillMount() { this.checkAuth(); } componentWillReceiveProps(nextProps) { this.checkAuth(); } checkAuth() { if (!this.props.isAuthenticated) { let redirectAfterLogin = this.props.location.pathname; this.props.dispatch(pushState(null, `/login?next=${redirectAfterLogin}`)); } } render() { return ( <div> {this.props.isAuthenticated === true ? <Component {...this.props}/> : null } </div> ) } } const mapStateToProps = (state) => ({ token: state.auth.token, userName: state.auth.userName, isAuthenticated: state.auth.isAuthenticated }); return connect(mapStateToProps)(AuthenticatedComponent); }
上面是做者給的用法,其中須要注意的是:
import {pushState} from 'redux-router';
因爲幾個react-router的區別這個問題,打包編譯後可能報錯,我項目中使用的是react-router-redux。
參考react-router-redux文檔中What if I want to issue navigation events via Redux actions?
使用方法是在AuthenticatedComponent中:
import {push} react-router-redux
也就是push代替了原例子中的pushState,做用都差很少。
而後就是加一箇中間件:
import { routerMiddleware, push } from 'react-router-redux' // Apply the middleware to the store const middleware = routerMiddleware(browserHistory) const store = createStore( reducers, applyMiddleware(middleware) )
這樣就能夠在AuthenticatedComponent中愉快地重定向了。
以上利用react-redux,redux-router || react-router-redux基於redux來保持登陸狀態和進行登陸權限驗證,能夠避免使用Cookie&localStroge等來保存登陸信息,其中的缺點就是,用戶刷新頁面或關閉瀏覽器後,登陸狀態就被銷燬了,若是有記住用戶名等需求,可能依然會用到本地存儲容器。
翻閱這個用例最後還發現做者已經寫了一個登陸權限驗證的library:
redux-auth-wrapper
不想搬運代碼的兄弟能夠參考文檔直接拿來用~
第一次寫文章,若是有概念錯誤的地方,請多多指正!!感謝!!