手记

【金秋打卡】第15天 使用 JWT 完成用户认证

课程名称:web前端架构师
课程章节:第16周 第五章 JWT 完成用户认证
主讲老师:张轩
课程内容: JWT 简介及优缺点、使用 JWT 完成用户认证

JWT 简介

JWT 官网 https://jwt.io/
基本工作流程

用户登录时,服务端生成 token 并返回给客户的,客户的保存起来,之后请求携带 token 进行访问,服务端会对用户携带的 token 进行验证

JWT token 的组成

Header:JSON对象,描述 JWT 的元数据,加密算法以及类型
Payload:JSON对象,存放数据需要传递的数据
Signature:对前两部分的签名,防止数据篡改。需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。

Token 优点

  • token 是无状态的(stateless),服务器不需要记录任何信息,不占用内存
  • 多进程,多服务器集群没有影响,易于扩展
  • 假如不记录在 cookie 中,没有跨域的影响
  • 和服务器端解耦,任何设备都可以生成token。

Token 的缺点

无法废弃,没有办法对快速对已经登录的用户做处理。
空间更大,所有数据是通过 base64进行编码的,会随着数据量的增大而变大。

使用 JWT 完成用户认证

使用 jsonwebtoken

npm i jsonwebtoken
npm i  @types/jsonwebtoken -D

生成 token

import jwt from 'jsonwebtoken'
const token = jwt.sign({ foo: 'bar' }, 'shhhhh');

验证 token

jwt.verify(token, 'shhhhh')

添加生成 token 方法

  public sign(data: any) {
    return jwt.sign(data, this.app.config.keys, {
      // algorithm: 'HS384',
      expiresIn: 60 * 60,
    });
  }

返回给登录用户

ctx.helper.success({
  ctx,
  res: {
    ...user.toJSON(),
    token: this.service.user.sign({ username: user.username }),
  },
  msg: '登录成功',
});

添加验证方法。客户的发送的请求头中会有 Authorization 这个字段,它的只就是 Bearer + 空格 + token

public validateToken() {
  const { authorization } = this.ctx.request.headers;
  if (authorization && typeof authorization === 'string') {
    const token = authorization.trim().split(' ')[1];
    if (token) {
      try {
        return jwt.verify(token, this.app.config.keys);
      } catch (error) {
        return false;
      }
    }
  }
  return false;
}

返回给登录用户

  public async getUser() {
    const { ctx } = this;
    const user = ctx.service.user.validateToken();
    if (user) {
      ctx.helper.success({
        ctx,
        res: user,
        msg: '获取用户信息成功',
      });
    } else {
      ctx.helper.error({
        ctx,
        errType: 'signValidateFail',
      });
    }
  }

使用 JWT 中间键完成用户认证

用户验证的逻辑肯定在很多地方会用到,所以需要将验证逻辑封装成一个中间键

// app/middleware/jwt.js
import jwt from 'jsonwebtoken';
import { Context } from 'egg';
import { Application } from 'egg';

// 获取并验证 token
function getToken(ctx: Context, app: Application) {
  const { authorization } = ctx.request.headers;
  if (authorization && typeof authorization === 'string') {
    const token = authorization.trim().split(' ')[1];
    if (token) {
      try {
        return jwt.verify(token, app.config.keys);
      } catch (error) {
        return false;
      }
    }
  }
  return false;
}

export default function (_options, app: Application) {
  return async (ctx: Context, next: () => Promise<any>) => {
    const decode = getToken(ctx, app);
    if (!decode) {
      return ctx.helper.error({
        errType: 'signValidateFail',
        ctx,
      });
    }
    // 将验证解密信息保存到 state 中
    ctx.state.user = decode;
    await next();
  };
}

在我们需要验证的接口加上该中间键

import { Application } from 'egg';

export default (app: Application) => {
  const { controller, router } = app;
  const jwt = app.middleware.jwt({}, app);
  router.get('/', controller.home.index);
  router.get('/user', jwt, controller.user.getUser);
};

在 controller 中根据验证信息获取用户信息

...
  public async getUser() {
    const { ctx, service } = this;
    // 根据解密的信息获取用户信息
    const user = await service.user.findByUsername(ctx.state.user.username);
    ctx.helper.success({
      ctx,
      res: user,
      msg: '获取用户信息成功',
    });
  }
...

0人推荐
随时随地看视频
慕课网APP