zoukankan      html  css  js  c++  java
  • 【React全家桶入门之十】登录与身份认证

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

    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相似,都是一个表单。

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

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

    终于效果

  • 相关阅读:
    电脑连接树莓派Pi Zero W
    HTTP 302报文
    解决跨域访问
    转chromeUI4
    转chromeUI3
    转chromeUI2
    转chromeUI
    OPM中细节设置
    CMFCButton导致PropertySheet窗口关闭
    [转]objectarx 加载菜单-ObjectARX中右键(快捷)菜单的实现方法
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8657762.html
Copyright © 2011-2022 走看看