React全家桶入门之十登录与身份认证
Posted 一韬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React全家桶入门之十登录与身份认证相关的知识,希望对你有一定的参考价值。
仔细想想,我们的后台系统还没有一个登录功能,太不靠谱,赶紧把防盗门安上!
SPA的鉴权方式和传统的web应用不同:由于页面的渲染不再依赖服务端,与服务端的交互都通过接口来完成,而REASTful风格的接口提倡无状态(state less),通常不使用cookie和session来进行身份认证。
比较流行的一种方式是使用web token,所谓的token可以看作是一个标识身份的令牌。客户端在登录成功后可以获得服务端加密后的token,然后在后续需要身份认证的接口请求中在header中带上这个token,服务端就可以通过判断token的有效性来验证该请求是否合法。
我们先来改造一下服务端,实现一个简单的基于token的身份认证(可直接复制代码,无需关心具体实现)。
改造服务端
先在根目录下执行npm i json-server -D
,虽然一开始以全局的方式安装过json-server这个工具,但本次要在代码中使用json-server的api,需要将其安装为项目依赖。
然后新建/server/auth.js
文件,写入以下代码:
const expireTime = 1000 * 60;
module.exports = function (req, res, next) {
res.header(‘Access-Control-Expose-Headers‘, ‘access-token‘);
const now = Date.now();
let unauthorized = true;
const token = req.headers[‘access-token‘];
if (token) {
const expired = now - token > expireTime;
if (!expired) {
unauthorized = false;
res.header(‘access-token‘, now);
}
}
if (unauthorized) {
res.sendStatus(401);
} else {
next();
}
};
新建/server/index.js
文件,写入以下代码:
const path = require(‘path‘);
const jsonServer = require(‘json-server‘);
const server = jsonServer.create();
const router = jsonServer.router(path.join(__dirname, ‘db.json‘));
const middlewares = jsonServer.defaults();
server.use(jsonServer.bodyParser);
server.use(middlewares);
server.post(‘/login‘, function (req, res, next) {
res.header(‘Access-Control-Expose-Headers‘, ‘access-token‘);
const {account, password} = req.body;
if (account === ‘admin‘ && password === ‘123456‘) {
res.header(‘access-token‘, Date.now());
res.json(true);
} else {
res.json(false);
}
});
server.use(require(‘./auth‘));
server.use(router);
server.listen(3000, function () {
console.log(‘JSON Server is running in http://localhost:3000‘);
});
修改/package.json
文件中的scripts.server
:
{
...
"scripts": {
"server": "node server/index.js",
...
},
...
}
然后使用npm run server
重启服务器。
现在我们的服务器就拥有了身份认证的功能,访问除了’/login’外的其它接口时,服务端会根据请求的header中access-token来判断请求是否有效,如果无效则会返回401状态码。
当客户端收到401的状态码时,需要跳转到登录页面进行登录,有效的管理员账号为admin,密码为123456。
以POST方法提交下面的参数到’http://localhost:3000/login‘接口,就能够完成登录。
{
"account": "admin",
"password": "123456"
}
登录成功后,接口返回true
,并且在返回的headers中包含了一个有效的access-token,用于在后面的请求中使用;登录失败则返回false
。
access-token的有效期为1分钟,每次有效的接口请求都会获得新的access-token;若1分钟内没有做操作,则会过期需要重新登录。
我们的access-token只是一个简单的timestamp,且没有做任何加密措施。
封装fetch
由于我们每个接口的请求都需要加上一个名为access-token的header,在每次需要调用接口的时候都写一遍就非常的不明智了,所以我们需要封装fetch方法。
新建/src/utils/request.js
,写入以下代码:
import { hashHistory } from ‘react-router‘;
export default function request (method, url, body) {
method = method.toUpperCase();
if (method === ‘GET‘) {
// fetch的GET不允许有body,参数只能放在url中
body = undefined;
} else {
body = body && JSON.stringify(body);
}
return fetch(url, {
method,
headers: {
‘Content-Type‘: ‘application/json‘,
‘Accept‘: ‘application/json‘,
‘Access-Token‘: sessionStorage.getItem(‘access_token‘) // 从sessionStorage中获取access token
},
body
})
.then((res) => {
if (res.status === 401) {
hashHistory.push(‘/login‘);
return Promise.reject(‘Unauthorized.‘);
} else {
return res.json();
}
});
}
export const get = url => request(‘GET‘, url);
export const post = (url, body) => request(‘POST‘, url, body);
export const put = (url, body) => request(‘PUT‘, url, body);
export const del = (url, body) => request(‘DELETE‘, url, body);
request方法封装了添加access-token头等逻辑,然后就可以在需要调用接口的时候使用request或get、post等方法了,比如/src/components/BookEditor.js
:
...
import request, {get} from ‘../utils/request‘;
class BookEditor extends React.Component {
...
handleSubmit (e) {
...
let editType = ‘添加‘;
let apiUrl = ‘http://localhost:3000/book‘;
let method = ‘post‘;
if (editTarget) {
...
}
request(method, apiUrl, {
name: name.value,
price: price.value,
owner_id: owner_id.value
})
.then((res) => {
if (res.id) {
...
} else {
...
}
})
.catch((err) => console.error(err));
}
getRecommendUsers (partialUserId) {
get(‘http://localhost:3000/user?id_like=‘ + partialUserId)
.then((res) => {
if (res.length === 1 && res[0].id === partialUserId) {
return;
}
...
});
}
...
}
...
其它还有/src/components/UserEditor.js
、/src/pages/BookEdit.js
、/src/pages/BookList.js
、/src/pages/UserEdit.js
和/src/pages/UserList.js
文件需要进行相应的修改。
实现登录页面
现在尝试访问一下用户列表页,发现表格里面并没有数据,因为没有登录接口访问被拒绝了并且尝试跳转到路由’/login’。
现在来实现一个登录页面组件,在/src/pages
下新建Login.js文件;
import React from ‘react‘;
import HomeLayout from ‘../layouts/HomeLayout‘;
import FormItem from ‘../components/FormItem‘;
import { post } from ‘../utils/request‘;
import formProvider from ‘../utils/formProvider‘;
class Login extends React.Component {
constructor () {
super();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit (e) {
e.preventDefault();
const {formValid, form: {account, password}} = this.props;
if (!formValid) {
alert(‘请输入账号或密码‘);
return;
}
post(‘http://localhost:3000/login‘, {
account: account.value,
password: password.value
})
.then((res) => {
if (res) {
this.context.router.push(‘/‘);
} else {
alert(‘登录失败,账号或密码错误‘);
}
})
}
render () {
const {form: {account, password}, onFormChange} = this.props;
return (
<HomeLayout title="请登录">
<form onSubmit={this.handleSubmit}>
<FormItem label="账号:" valid={account.valid} error={account.error}>
<input type="text" value={account.value} onChange={e => onFormChange(‘account‘, e.target.value)}/>
</FormItem>
<FormItem label="密码:" valid={password.valid} error={password.error}>
<input type="password" value={password.value} onChange={e => onFormChange(‘password‘, e.target.value)}/>
</FormItem>
<br/>
<input type="submit" value="登录"/>
</form>
</HomeLayout>
);
}
}
Login.contextTypes = {
router: React.PropTypes.object.isRequired
};
Login = formProvider({
account: {
defaultValue: ‘‘,
rules: [
{
pattern (value) {
return value.length > 0;
},
error: ‘请输入账号‘
}
]
},
password: {
defaultValue: ‘‘,
rules: [
{
pattern (value) {
return value.length > 0;
},
error: ‘请输入密码‘
}
]
}
})(Login);
export default Login;
登录页面组件和UserEditor或者BookEditor类似,都是一个表单。
在这里提交表单成功后跳转到首页。
最后,别忘了加上登录页面的路由。
最终效果
以上是关于React全家桶入门之十登录与身份认证的主要内容,如果未能解决你的问题,请参考以下文章
React自制全家桶React使用ref操作DOM与setState遇到的问题