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

细致想一想。咱们的后台系统尚未一个登陆功能,太不靠谱,赶忙把防盗门安上!javascript

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

比較流行的一种方式是使用web token,所谓的token可以看做是一个标识身份的令牌。java

client在登陆成功后可以得到服务端加密后的token,而后在兴许需要身份认证的接口请求中在header中带上这个token。服务端就可以经过推断token的有效性来验证该请求是否合法。node

咱们先来改造一下服务端。实现一个简单的基于token的身份认证(可直接复制代码。无需关心详细实现)react

改造服务端

先在根文件夹下运行npm i json-server -D,尽管一開始以全局的方式安装过json-server这个工具。但本次要在代码中使用json-server的api,需要将其安装为项目依赖。web

而后新建/server/auth.js文件,写入下面代码:npm

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文件,写入下面代码:json

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.serverapi

{
  ...
  "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类似,都是一个表单。

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

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

终于效果

相关文章
相关标签/搜索