您好, 假如喜欢我的文章或者想上岸大厂,能够重视大众号「量子前端」,将不定时重视推送前端好文、共享就业资料秘籍,也期望有时机一对一帮助你完成愿望

JWT身份鉴权计划,token会作为主要的鉴权办法来作为前后端通讯校验的凭证,当该token被篡改或者直接被第三方拿到,就能够假造该用户做一系列事务操作,是一种十分严重的安全漏洞。

那,JWT是啥?假如你不知道,凭借AI的力气,看看它的解说:

JWT(JSON Web Tokens)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。因为其信息能够被验证和信任,JWT一般用于身份验证和信息交流。因为其较小的尺寸,它们特别适合于空间受限的环境,例如HTTP头部。

JWT主要由三个部分组成:

  1. Header(头部):Header一般由两部分组成,令牌的类型(即JWT)以及所运用的签名算法(如HMAC SHA256或RSA)。
  2. Payload(负载):其中包含所谓的Claims(声明),Claims是有关实体(一般是用户)和其他数据的声明。有三种类型的Claims:注册的声明、公共的声明和私有的声明。
  3. Signature(签名):为了获取这部分的签名,必须有编码过的header(头部)、编码过的payload(负载)、一个密钥,经过header中指定的算法进行签名。签名用于验证消息在传输途中没有被更改,并且,关于运用私有秘钥签署的token,还能够验证恳求方是否为token的合法发布者。

JWT的运用流程一般如下:

  1. 用户运用各种办法(如用户名和暗码)向认证服务器进行身份验证。
  2. 一旦服务器验证了用户的身份,它将生成一个JWT,并将其作为呼应回来给用户。
  3. 用户随后将此令牌用作每次向服务器恳求资源时的凭证。一般,这个令牌放在HTTP恳求的Authorization头部中,带上’Bearer’前缀。
  4. 服务器会查验这个JWT的signature来确认其有效性,然后回来恳求的数据。

JWT运用上述机制使得用户状况无需在服务器持久存储,从而更容易完成无状况、可扩展的应用,这在散布式微服务架构中特别有用。同时,JWT还提供了一种简单的办法,能够确保数据传输过程中的数据安全性和完好性。

假造恳求怎么办?看这篇就够了

介绍完JWT,那防止token假造有没有什么好的计划?关于安全防御问题,中心战略便是“提升破解难度”,咱们能够在客户端和服务端的通讯中参加一个额外的约好签名,签名能够由当时时刻戳信息、设备ID、日期、双方约好好的秘钥经过一些加密算法构造而成加密解密办法相互约好好,这样攻击者就算拿到了Token,还需要知道签名的生成规矩和加密办法,才能够达成攻击,就像这样:

假造恳求怎么办?看这篇就够了

其实就相当于多了一层校验逻辑判别。

那接下来咱们来完成一下吧。

咱们先起个Nest项目,执行命令:

nest new jwt-project --strict

要做身份验证,那咱们就预备好两个接口————logingetUserInfo

interface loginDTO {
  access_token: string;
  msg: string;
}
interface getUserInfoDTO {
  name: string;
  id: number;
}
@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
  ) {}
  @Post('login')
  login(): loginDTO {
    return this.appService.login();
  }
  @Post('getUserInfo')
  getUserInfo(): getUserInfoDTO {
    return this.appService.getUserInfo();
  }
}

启动项目:

npm run start

浏览器上拜访一下login接口,项目现已跑起来了

假造恳求怎么办?看这篇就够了

接口预备好了,咱们完成login的逻辑,数据库这儿就不预备了,关于用户信息判别咱们直接在Nest保护一个数组来存储,并且引进NestJWT服务。

假造恳求怎么办?看这篇就够了

咱们提前预备一下,首要安装依靠包。

npm i @nestjs/jwt

在模块中引进:

假造恳求怎么办?看这篇就够了

这样login接口就会根据user的信息生成一个JWT字符串秘钥回来,调用一次接口试一下:

假造恳求怎么办?看这篇就够了

有了Token就能够验完好的会话流程了,客户端一般咱们会把Token存在本地存储,咱们起一个客户端项目试一下,后端项目咱们先开一下cors,支撑跨域拜访。

假造恳求怎么办?看这篇就够了

咱们起一个前端项目,用umi.js来作为主结构,执行命令:

npx create-umi@latest

找一个页面文件,写入调用login的代码:

假造恳求怎么办?看这篇就够了

为了便利测试,页面刷新咱们就用调一次login,更新一次Token,并把它存在本地存储中,接下来咱们预备一下事务接口。

假造恳求怎么办?看这篇就够了

关于Token做了为空和真实性两层校验,咱们测试一下,在前端页面先把Token给删了,调一次:

假造恳求怎么办?看这篇就够了

假造恳求怎么办?看这篇就够了

恳求头没有authorization字段,后端报了401,符合预期,咱们再篡改一下Token试试:

假造恳求怎么办?看这篇就够了

调一次getUserInfo,抛了NestJWT校验失利的异常:

假造恳求怎么办?看这篇就够了

OK,至此JWT的登录身份验证部分现已完成了,但现在有一个问题,咱们的Token是暴露在浏览器的,假如我仿制了这个Token,去postman调一下,是不是也能够调通?

测试一下。

假造恳求怎么办?看这篇就够了

毫无意外,翻车了。这也回到了文章标题,那如何处理这种状况呢?最初也提到了,加一层认证即可,在传Token的鉴权接口咱们再多加一个sign字段,也放在header,这儿的加解密咱们用crypto-js,咱们先完成下前端代码,首要安装依靠包。

npm i crypto-js --save-dev

然后在测试页面中引进,咱们选用客户端加密sign、服务端解密sign的计划,因而咱们完成个加密办法:

假造恳求怎么办?看这篇就够了

加密需要秘钥和秘钥偏移量,这两个参数前端和后端保持共同,这点十分重要。然后咱们在getUserInfo接口中的恳求头给它带上去。

假造恳求怎么办?看这篇就够了

加密的基准字符串选用调用时刻、用户名、Token前6位组成,中心用冒号隔开,就像这样:

"1707449642869:jack:eyJhbG"

然后咱们完成一下后端代码,相同也是先把依靠包安装,这是通用JS包,所以安装办法一样,然后在Nest中完成下解密验证的逻辑即可:

假造恳求怎么办?看这篇就够了

在后端咱们加上解密的办法,相同根据密钥、密钥偏移量,与客户端共同,咱们在事务接口中关于Token验证的基础上,再增加一层关于sign的验证,加强了接口安全,咱们测试一下。

首要只传Token现在现已不行了:

假造恳求怎么办?看这篇就够了

后端默认运用第一个user来回来的,那前端咱们传一个不一样的username作为sign,加上一层sign定制判别逻辑:

 if (userName !== 'aaa') {
    throw new UnauthorizedException('用户信息错误');
  }

再调一下,header都传上:

假造恳求怎么办?看这篇就够了

命中了咱们加上的判别逻辑,看一眼打印出来解密的sign日志:

假造恳求怎么办?看这篇就够了

打印出来的用户名是jack,不是aaa,当然校验失利了,这下咱们的假造处理计划完成了,咱们能够把这一层逻辑悉数集成在一个中心件上。

封装一下代码,新增middlewares/auth.middleware.ts

import {
  Injectable,
  NestMiddleware,
  UnauthorizedException,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt';
import * as CryptoJS from 'crypto-js';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private readonly jwtService: JwtService) {}
  private readonly SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161'); //十六位十六进制数作为密钥
  private readonly SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); //十六位十六进制数作为密钥偏移量
  private decrypt(data: string): string {
    //解密
    const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
    const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decrypt = CryptoJS.AES.decrypt(str, this.SECRET_KEY, {
      iv: this.SECRET_IV,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
    });
    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
  }
  use(req: Request, res: Response, next: NextFunction) {
    try {
      const token = req.headers['authorization'];
      const sign = req.headers['sign'];
      if (!token) {
        throw new UnauthorizedException('登录态失效,请从头登录');
      }
      if (!sign) {
        throw new UnauthorizedException('身份验证失利');
      }
      //   token校验
      this.jwtService.verify(token);
      //   sign校验
      const signRes = this.decrypt(sign as string);
      const [time, userName, tokenStr] = signRes.split(':');
      console.log('time:', time);
      console.log('userName:', userName);
      console.log('tokenStr:', tokenStr);
      if (userName === '' || time === '' || tokenStr === '') {
        throw new UnauthorizedException('身份验证失利');
      }
      if (userName !== 'aaa') {
        throw new UnauthorizedException('用户信息错误');
      }
      return next();
    } catch (error) {
      throw new UnauthorizedException(
        error.message || 'Unauthorized: Invalid token',
      );
    }
  }
}

然后在Nest中敞开这个中心件,咱们目前login接口不需要校验,所以只开getUserInfo接口。

假造恳求怎么办?看这篇就够了

再跑一遍,没问题,结束。

咱们简单总结一下。

JWT是目前主流的会话鉴权计划,但是不做安全计划的状况下,直接把Token暴露在恳求体中会引发假造身份恳求的问题。

咱们经过在恳求中多一层加解密的约好式校验在通讯中增加了网络恳求的安全性。

假如喜欢我的文章或者想上岸大厂,能够重视大众号「量子前端」,将不定时重视推送前端好文、共享就业资料秘籍,也期望有时机一对一帮助你完成愿望。