细致想想,我们的后台系统还没有一个登录功能,太不靠谱,赶紧把防盗门安上!

SPA的鉴权方式和传统的web应用不同:因为页面的渲染不再依赖服务端,与服务端的交互都通过接口来完毕,而REASTful风格的接口提倡无状态(state less),通常不使用cookie和session来进行身份认证。

比較流行的一种方式是使用web token,所谓的token能够看作是一个标识身份的令牌。client在登录成功后能够获得服务端加密后的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状态码。

当client收到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 {
const token = res.headers.get('access-token');
if (token) {
sessionStorage.setItem('access_token', token);
}
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全家桶入门之十】登录与身份认证的更多相关文章

  1. react 项目实战(九)登录与身份认证

    SPA的鉴权方式和传统的web应用不同:由于页面的渲染不再依赖服务端,与服务端的交互都通过接口来完成,而REASTful风格的接口提倡无状态(state less),通常不使用cookie和sessi ...

  2. 【React全家桶入门之十三】Redux中间件与异步action

    在上一篇中我们了解到,更新Redux中状态的流程是这种:action -> reducer -> new state. 文中也讲到.action是一个普通的javascript对象.red ...

  3. React全家桶入门

    http://blog.csdn.net/column/details/14545.html

  4. 使用react全家桶制作博客后台管理系统

    前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基于react全家桶(React.React-r ...

  5. react-music React全家桶项目,精品之作!

    React-Music 全家桶项目,精品之作! 一.简介 该项目是基于React全家桶开发的一个音乐播放器,技术栈采用:Webpack + React + React-redux + React-ro ...

  6. 初学者的React全家桶完整实例

    概述 该项目还有些功能在开发过程中,如果您有什么需求,欢迎您与我联系.我希望能够通过这个项目对React初学者,或者Babel/webpack初学者都有一定的帮助.我在此再强调一下,在我写的这些文章末 ...

  7. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  8. webpack4 中的最新 React全家桶实战使用配置指南!

    最新React全家桶实战使用配置指南 这篇文档 是吕小明老师结合以往的项目经验 加上自己本身对react webpack redux理解写下的总结文档,总共耗时一周总结下来的,希望能对读者能够有收获, ...

  9. React全家桶+Material-ui构建的后台管理系统

    一.简介 一个使用React全家桶(react-router-dom,redux,redux-actions,redux-saga,reselect)+Material-ui构建的后来管理中心. 二. ...

随机推荐

  1. Python 爬虫实例(14) 爬取 百度音乐

    #-*-coding:utf-8-*- from common.contest import * import urllib def spider(): song_types = ['新歌','热歌' ...

  2. 定期删除elasticsearch 的index 索引

    #!/bin/bashfind /data/elasticsearch/data/pro-kz-log/nodes/0/indices/ -type d -mtime +7 | awk -F" ...

  3. The superclass "javax.servlet.http.HttpServlet" was not found 问题解决

    项目中报" The superclass "javax.servlet.http.HttpServlet" was not found "这个错误,是因为缺少t ...

  4. 用bundler安装jeklly

    为什么要写这篇文章呢?因为官方的安装文档里,ruby的很多库没有说明怎么安装.所以需要重点说明一下.1.我的安装环境是vultr的16.04版的ubuntu.2.因为ruby的扩展库好多都是Gcc编译 ...

  5. FFmpeg(8)-打开音视频解码器,配置解码器上下文(avcodec_find_decoder()、avcodec_alloc_context3())

    一.avcodec_find_decoder 获取解码器.在使用之前必须保证所用到的解码器已经注册,最简单的就是调用avcodec_register_all() 函数,就像之前注册解封装器的时候,也要 ...

  6. angular控制器的执行顺序和服务的注入情况

    这篇文章到底要讲什么呢? 这必须要从栗子开始讲起... 看下面这两段代码: demo1: http://jsfiddle.net/ujzmvp3j/1/ demo2: http://jsfiddle. ...

  7. (原创)舌尖上的c++--相逢

    引子 前些时候,我在群里出了一道题目:将变参的类型连接在一起作为字符串并返回出来,要求只用函数实现,不能借助于结构体实现.用结构体来实现比较简单: template<typename... Ar ...

  8. (转)CTP: 平昨仓与平今仓,log轻轻告诉你.......

    转自:http://blog.csdn.net/wowotuo/article/details/43242663 CTP的相关文档告诉我们,中金所和三大商品交易所中,只有上期所区分平今仓和平昨仓.也就 ...

  9. [开源项目-MyBean轻量级配置框架] MyBean的特性和MyBean的开始

    [概述] 自从mBean框架出生后,受到很多朋友的关注,在公司的外包项目中得到了不错的应用.由于mBean是公司的项目,不便开源,于是这几天利用晚上的时间和周末的时间重写了底层beanMananger ...

  10. XCode 7 运行 cocos2dx 2.2.6问题小节

    终于磕磕绊绊的在模拟器上,成功运行了已有项目. 公司提供的Mac系统,版本炒鸡低.向同事拷贝了OS,和XCode. 安装完成后,将已有的可以在Windows上成功运行Android版本的项目,拷贝到了 ...