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

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

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

最终效果

时间: 2024-10-10 16:20:10

【React全家桶入门之十】登录与身份认证的相关文章

vue全家桶和react全家桶

vue全家桶:vue  +  vuex (状态管理)  + vue-router (路由) + vue-resource +axios +elementui react全家桶 : react + redux(状态管理) +react-router(路由) + axios + antd || antd-model 原文地址:https://www.cnblogs.com/aibabel/p/11827851.html

react全家桶从0搭建一个完整的react项目(react-router4、redux、redux-saga)

react全家桶从0到1(最新) 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redux-saga的核心配置会加以讲解,通过这个项目,可以系统的了解react技术栈的主要知识,避免搭建一次后面就忘记的情况. 从webpack开始 思考一下webpack到底做了什么事情?其实简单来说,就是从入口文件开始,不断寻找依赖,同时为了解析各种不同的文件加载相应的loader,最后生成我们希望的

Kubernetes之(十五)身份认证,授权,准入控制

目录 Kubernetes之(十五)身份认证,授权,准入控制 ServiceAccount 创建serviceaccount 自定义serviceaccount 自建证书和账号进行访问apiserver RBAC简介 Kubernetes RBAC演示 RBAC的三种授权访问 Kubernetes之(十五)身份认证,授权,准入控制 API Server作为Kubernetes网关,是访问和管理资源对象的唯一入口,其各种集群组件访问资源都需要经过网关才能进行正常访问和管理.每一次的访问请求都需要进

react全家桶-1

全家桶内装有: react - github react-router - github redux - github react-redux - github react-router-redux - github redux-saga - github immutable - github reselect - github antd - github 服务端: json server 作为工具,支持CORS和JSONP跨域请求,支持GET, POST, PUT, PATCH 和 DELET

React全家桶+AntD 共享单车后台管理系统开发

第1章 课程导学对课程整体进行介绍,并且说明学习的必要性. 1-1 课程导学第2章 React基础知识React基础知识以及生命周期的介绍,并使用React官方脚手架初始化基础项目,同时介绍了新一代打包工具Yarn. 2-1 React基础介绍2-2 React脚手架使用2-3 React生命周期介绍第3章 主页面架构设计详细介绍了初始项目的插件安装.主题定制以及主页面结构设计. 3-1 基础插件安装(1)3-2 基础插件安装(2)3-3 页面结构开发(1)3-4 页面结构开发(2)3-5 菜单

2019教你用react全家桶+node.js全栈开发大型电商后台管理系统(视频+源码+课件)

主要内容:1. 业务功能模块: 用户登陆.商品分类管理.商品管理.角色管理.用户管理.菜单权限控制.订单管理等2. 前端技术: React + React Router4 + Redux + Antd + Axios + ES6/ES8 + webpack + ECharts/Bizcharts 等 3. 后端技术: NodeJS + Express + MongoDB + Mongoose + Multer 等 4. 项目开发模式: 模块化.组件化.工程化的开发模式 5. 深入源码: 自定义R

react基础---react全家桶03

目录: 1. redux 1.1 原始,原始步骤 1.2 react-reducer,两种写法(导出普通写法 和 装饰器的写法) 1.3 存储多个reducer 2. redux中间键,redux-logger | redux-thunk 异步请求,调用dispatch 3. router 基本:BrowserRouter, Link, Route, Switch, Redirect(tab中默认页面),404 传参 路由守卫 内容: 1. redux 安装 #npm install redux

React全家桶之一 react-router之高级

使用query获取URL中的参数 //引入相关的依赖 const Page = props => <div> <h1>{props.location.query.message}</h1> <div> 然后在URL中输入http://localhost:8080/#/?message=wn, 页面中就会显示wn 另外我们还可以在组件中Link中设置pathname和query变量 //依然是引入各种依赖 <Link to={{ pathname:

SSO单点登录统一身份认证系统

什么是单点登录 简单点说就是公司有A,B两个系统,我登录了A系统之后再跳转到B系统可以直接访问,而不需要再次登录B系统. 几种常见的单点登录实现方式 在讲解单点登录之前先讲解几个基本的概念: Cookie: Cookie是一段不超过4KB的小型文本数据,是保存在用户本地的,常见格式为: Expires属性:设置Cookie的生存期 Domain属性:指定了可以访问该 Cookie 的 Web 站点或域 比如图中的Domain:192.168.1.72这就表示只能只有1.72下的请求可以使用这个c