【React全家桶入门之十】登录与身份认证

来源:互联网 发布:学校收费软件 编辑:程序博客网 时间:2024/06/06 15:42

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

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();  }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

新建/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');});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

修改/package.json文件中的scripts.server

{  ...  "scripts": {    "server": "node server/index.js",    ...  },  ...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然后使用npm run server重启服务器。

现在我们的服务器就拥有了身份认证的功能,访问除了’/login’外的其它接口时,服务端会根据请求的header中access-token来判断请求是否有效,如果无效则会返回401状态码。

当客户端收到401的状态码时,需要跳转到登录页面进行登录,有效的管理员账号为admin,密码为123456。

以POST方法提交下面的参数到’http://localhost:3000/login‘接口,就能够完成登录。

{  "account": "admin",  "password": "123456"}
  • 1
  • 2
  • 3
  • 4

登录成功后,接口返回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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

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;        }        ...      });  }  ...}...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

其它还有/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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

登录页面组件和UserEditor或者BookEditor类似,都是一个表单。

在这里提交表单成功后跳转到首页。

最后,别忘了加上登录页面的路由。

最终效果

版权声明:本文为博主原创文章,转载请注明来源。博客地址:http://blog.csdn.net/awaw00

阅读全文
0 0
原创粉丝点击