最近使用React和Redux構建一個後臺專案,在做登入系統的時候,看了網上很多資料,一般都是使用sessionStorage(包括Cookie,下略)或者localStorage儲存從伺服器獲取的token,然後使用react-router中onEnter這個方法依據sessionStorage或者localStorage中是否存在相應的token來判定登入狀態。
Cookie, LocalStorage 與 SessionStorage的詳解可以參考:詳說 Cookie, LocalStorage 與 SessionStorage一文。
react-router的onEnter方法使用可以參考react-router官方的用例:auth-flow。
倉庫地址:https://github.com/reactjs/re…
auth-flow這個用例使用localStorage來儲存token,react-router的onEnter呼叫requireAuth方法來判斷auth.loggedIn()是否能正確返回localStorage.token,來維持登入狀態,這也是目前常用的做法。
app.js
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-example
這個用例使用了一個高階函式(high-order function,用例中為requireAuthentication)來包裝需要登入許可權的Compenent(用例中為ProtectedView),這個Compenent位於所有需要登入許可權的頂層:
routers.js
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
不想搬運程式碼的兄弟可以參考文件直接拿來用~
第一次寫文章,如果有概念錯誤的地方,請多多指正!!感謝!!