Eggjs快速入门4:egg使用cookie-session和jwt实现登录

2,363 阅读4分钟

这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。

1.登录注册流程

实现用户的登录注册,主要具有两个方案cookie-session模式jwt模式

具体实施方案具有 cookie-session模式

1、请求方发送账户密码到服务器,验证账户可用性。
2、验证成功,生成 session,保存在服务端。
3、服务端返回一个 session ID, 保存在客户端 cookie 中。
4、客户端请求时携带 cookie。
5、服务器解析 cookie 中携带的 session ID,验证通过后返回响应数据。

jwt模式

1.用户在系统注册输入用户名,密码等信息,后端保存在用户表里
2.用户在系统登录输入用户名,密码等信息,后端验证信息正确性,验证信息正确给给前端一个特殊的标识符token
3.后续需要校验的接口,前端直接带上标识符token给后端用于解析识别身份,再进行后续处理

2.egg后端登录注册cookie,seesion方案

cookie和session的关系

cookie是浏览器维护的一个加密字符串,session是后端维护的一个键值对,键就是前端的cookie。前端的cookie传给后端,后端根据cookie做为键去维护的session键值对遍历寻找指定的值,用于区分用户的身份。

如何写入cookie

浏览器在请求服务器的时候,比如浏览器是localhost:3000,服务器123.12.12.89:7001。浏览器域名localhost:3000请求服务器123.12.12.89:7001/setcookie的某个接口,在指定写入了cookie,egg.js中使用ctx.cookies.set(key,value),然后浏览器访问localhost:3000的时候,查看application的cookie发现会有一个123.12.12.89域的cookie字段,注意只有指定前端域会写入。理论上是这样,但是你看到的结果是没有写入浏览器,原因如下

cookie session支持同域(同一级域名即可,二级域名和端口都可以不同)

这个时候本地调试就需要前端配置host

font.morant.com 前端ip
end.morant.com  后端ip
复制代码

配置了host的作用是,每次请求都使用域名请求访问,这样式同域请求,但是会映射成不同的ip,那么同域的目的达到了。这个时候就可以看见font.morant.com 被写入了cookie,中间可能会出现跨域,这个方式较多,容易解决。

自动携带cookie

按道理浏览器会携带请求的接口域名是否在本域下的cookie,所以如果你的接口是get请求,并且浏览器请求,那么你会发现能够自动携带,但是如果ajax请求那么就会出现问题

ajax携带cookie

前端axios设置axios.defaults.withCredentials = true;

后端设置

  config.cors = {
    origin: '前端指定源+端口',
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
    // 下面这条加上才能共享跨域session,同时前端ajax请求也要加上响应的参数
    credentials: true,
  };

复制代码

这个时候你发现写入的cookie第二次刷新能够自动携带在请求头上了

3.egg登录注册JWT方案

JSON Web Token(简称JWT)是目前最流行的跨域认证解决方案。是一种认证授权机制。JWT是为了在网路应用环境间传递声明而执行的一种基于JSON的开放标志()。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便从资源服务器获取资源。比如用在用户登录上。

采用上节特别讲解到中间件的方案处理是很合适的,所有请求之前都通过中间件鉴权。

1.安装egg-jwt,用户签发token和解密token
npm i egg-jwt --save
2.在plugin.js配置插件,并且开启
// 配置单点登录
exports.jwt = {
  enable: true,
  package: 'egg-jwt',
};

3.首先手动设置一个加密盐scret放在config.default.js,方便修改存取统一配置
  config.jwt = {
    secret: "123456",
  };
4.拿到username,password在数据库查询比对是否正确,密码可能会进行加密解密处理放数据库里
5.签发token(将config.default.js中的secret取出来用于加密,data用于加密数据库标识符比如userId)
 const token=this.app.jwt.sign(data, this.app.config.jwt.secret, { expiresIn: 30 * 24 * 60 * 60 + 's' }
6.将token当做请求返回值给前端存储
7.真正校验中间件逻辑
'use strict';

// middleware/jwtErr.js
module.exports = options => {
  return async function jwtErr(ctx, next) {
    let token = '';
    if (
      ctx.headers.authorization && ctx.headers.authorization.split(' ')[0] === 'Bearer'
    ) {
      token = ctx.headers.authorization.split(' ')[1];
    } else if (ctx.query.accesstoken) {
      token = ctx.query.accesstoken;
    } else if (ctx.request.body.accesstoken) {
      token = ctx.request.body.accesstoken;
    }
    let decode = '';
    if (token) {
      try {
        // 解码token
        decode = ctx.checkToken(token, options.secret);

        // 判断是否解码
        if (!decode) {
          ctx.returnBody(false, {}, 'Token 无效,请重新登录', 401);
          return;
        }

        if (decode && decode.exp <= new Date() / 1000) {
          ctx.responseData({}, 'Token已过期,请重新登录', 401, 401);
        }
        // 解码的数据绑定在请求上
        ctx.request.decode = decode;
        await next();
      } catch (error) {
        ctx.responseData({}, 'token无效,请重新上传', 401, 401);
        return;
      }
    } else {
      ctx.responseData({}, '没有token,请上传token', 401, 401);
      return;
    }
  };
};


8.规定哪些接口需要token校验(也可以配置中间件全局校验)
  const { router, controller, middleware } = app;
  const jwtErr = middleware.jwtErr(app.config.jwt);
  router.get("/userinfo", jwtErr, controller.user.findUserList);//查询用户信息
9.前端将token待在请求头里,authorization中选择bear-token

image.png