Skip to content

Nest 使用 双 token 和单 token

双 token 和单 token 的

目的都是为了做到无感刷新.让用户感觉不到刷新,只是后台自动刷新了 token

  • 双 token 是长换短 token,短 token 过期时间短,长 token 过期时间长,短 token 过期了,用长 token 换取短 token,长 token 过期了,重新登录

  • 单 token 就是在守卫里面后台设置头部 token

双 token

  • 流程就是 登录 接口 获取到 双 token 一个短 一个长

  • 在出一个刷新接口,接口里面获取到 长 token 换取短 token,然后返回给前端,前端存起来,每次请求都带上短 token

  • 如果长 token 过期了,重新登录

配置文件

  • dev.yml
bash
# jwt 配置 d代表天 h代表小时 m分钟  s代表秒
jwt:
  secretkey: 'jsopy'
  expiresin: '5s'
  refreshExpiresIn: '2h'

其他环境相同

jwt.service.ts

ts
import { Injectable, Inject } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class JwtServiceAll {
  // 注入配置文件
  @Inject()
  private readonly configService: ConfigService;
  constructor(private readonly jwtService: JwtService) {}

  async setToken(userInfo: any) {
    // 这里不能写用户密码,怕破译
    const payload = {
      sub: userInfo.id, // sub 就是用户名称
      phonenum: userInfo.phonenum, // phonenum 就是手机号 换成你自己的
    };
    const result = await this.jwtService.sign(payload); // 设置短时间采取默认
    // 生成token
    return result;
  }

  verifyToken(token: string) {
    return this.jwtService.verify(token, {
      secret: this.configService.get("jwt").secretkey,
    });
  }

  // refreshToken
  async setRefreshToken(userInfo: any) {
    const payload = {
      sub: userInfo.id, // sub 就是用户名称
      username: userInfo.username, // username 就是用户姓名
    };
    const result = await this.jwtService.sign(payload, {
      expiresIn: this.configService.get("jwt").refreshExpiresIn, // 设置长时间
    });
    return result;
  }
}

user.service.ts

ts
  // 2. 测试jwt
async testjwt() {
  // 这里需要验证下密码 我省略了
  // 返回一个短token 一个长token 手机号换成你需要的字段
  const accsee_token = await this.jwtService.setToken({
    id: 1,
    phonenum: '1234567890',
  });
  const refresh_token = await this.jwtService.setRefreshToken({
    id: 1,
    phonenum: '1234567890',
  });
  const result = {
    accsee_token,
    refresh_token,
  };
  return result;
}

user.controller.ts

  • 一个登录的时候 会返回 两个 token

  • 一个刷新的时候 会返回 两个 token

前端当 accsee_token 过期了,用 refresh_token 去刷新接口,刷新接口会返回两个 token,前端再存起来,每次请求都带上短 token

ts
  @Post('testjwt')
  async testjwt() {
    const token = await this.userService.testjwt();
    return {
      data: {
        token,
      },
    };
  }

单 token

  • 用的少

每次访问都自己刷新 token.前端存下来,每次请求都带上 token

修改 global.guard.ts

ts
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { GlobalGuardException } from "./globalguard.exception";
import { ConfigService } from "@nestjs/config";
import { JwtServiceAll } from "../modules/jwt/jwt.service";
@Injectable()
export class GlobalGuard implements CanActivate {
  @Inject()
  private configService: ConfigService;
  @Inject()
  private jwtService: JwtServiceAll;
  async canActivate(context: ExecutionContext): Promise<boolean> {
    //权限验证
    const request = context.switchToHttp().getRequest();
    // 白名单
    const whiteList = this.configService.get("perm").router.whiteList;
    // 验证
    if (whiteList.includes(request.url)) {
      return true;
    } else {
      // 判断
      const authorization = request.header("Authorization") || "";

      const bearer = authorization.split(" ");

      if (!bearer || bearer.length < 2) {
        const result = JSON.stringify([{ message: "请输入token" }]);
        throw new GlobalGuardException(result);
      }

      const token = bearer[1];
      try {
        const info = this.jwtService.verifyToken(token);
        // 我这里是解析出来后把userId赋值给request 这样控制器就拿到了
        (request as any).userId = info.sub; // 换成你自己的payload对应字段
        // 每次访问都刷新token
        const response = context.switchToHttp().getResponse();
        const newToken = await this.jwtService.setToken({
          id: 1, // 内容换成你自己的
          username: "张三", // 内容换成你自己的
        });
        // 设置了新的token
        response.setHeader("Authorization", `Bearer ${newToken}`);
        return true;
      } catch (e) {
        const result = JSON.stringify([{ message: "token 错误" }]);
        throw new GlobalGuardException(result);
      }
    }
  }
}

增加跨域

  • main.ts
ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

// 改变端口 增加跨域
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({
    exposeHeaders: "Authorization", // 换成你自己给前端返回的字段名
  });
  await app.listen(process.env.PORT ?? 5000);
}
bootstrap();

前端

  • axios 里面拦截器
ts
axios.interceptors.response.use((response) => {
  const newToken = response.headers["Authorization"];
  if (newToken) {
    localStorage.setItem("Authorization ", newToken);
  }
  return response;
});