nestJS的JWT身份认证学习过程

2,368 阅读3分钟

四、nestJS的JWT身份认证

1.身份认证

客户端将首先使用用户名和密码进行身份验证。一旦通过身份验证,服务器将发出 JWT,该 JWT 可以在后续请求的授权头中作为 token发送,以验证身份验证。我们还将创建一个受保护的路由,该路由仅对包含有效 JWT 的请求可访问。


$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
  • 新建模块auth
$ nest g module auth
$ nest g service auth

2.安装JWT安装包

$ npm install @nestjs/jwt passport-jwt
$ npm install @types/passport-jwt --save-dev

3.各个模块功能实现过程

  • constants.ts文件自定义JWT密匙

/**
 *jwt密匙
 */

export const jwtConstants = {
  secret: 'secretKeyXXXX',
};
  • jwt.strategy.ts文件定义默认使用JWT策略,可以定义多种策略模式

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

/**
 * @两种策略模式:1.jwt认证
 */

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false, // 请求被拒默认返回401未经授权的错误码
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return { id: payload.sub, username: payload.username };
  }
}
  • auth.service.ts模块调用UserService的请求方法验证用户,我们使用 @nestjs/jwt 库,该库提供了一个 sign() 函数,用于从用户对象属性的子集生成token

import { Injectable } from '@nestjs/common';
import { UserService } from '@/user/user.service';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    private readonly UserService: UserService,
    private readonly jwtService: JwtService,
  ) {}

  /**
   * @本地身份策略调用方法
   * @调用UserService里面的findUserName方法通过username找到用户
   * @param username
   * @param password
   */
  async validateUser(username: string, password: string): Promise<any> {
    const user = await this.UserService.findUserName(username);
    if (user[0] && user[0].password === password) {
      return user;
    }
    return null;
  }

  /**
   * @jwt认证登录
   * @param user
   */
  async login(user: any) {
    const payload = { username: user.username, sub: user.id };
    return {
      code: 200,
      data: {
        id: user.id,
        username: user.username,
      },
      access_token: this.jwtService.sign(payload),
    };
  }

  /**
   * @token验证方法
   * @param token
   */
  async verifyToken(token: string): Promise<any> {
    try {
      if (!token) return false;
      const id = this.jwtService.verify(token.replace('Bearer ', ''));
      return id;
    } catch (e) {
      return false;
    }
  }
}
  • auth.controller.ts生成路由通过/auth/login接口登录

import { Controller, Get, UseGuards, Request, Post } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { LocalAuthGuard } from '@/auth/local-auth.guard';
import { AuthService } from '@/auth/auth.service';
import { ApiTags } from '@nestjs/swagger';
@Controller()
export class AuthController {
  constructor(private readonly authService: AuthService) {}
  /**
   * @通过UseGuards装饰器引用本地策略拿到返回的用户
   * @返回token
   * @param req
   */
  @ApiTags('用户登录')
  @UseGuards(LocalAuthGuard)
  @Post('auth/login')
  async login(@Request() req) {
    return this.authService.login(req.user);
  }

  /**
   * @验证登录信息:通过token解析出用户id用户名
   * @param req
   */
  @UseGuards(AuthGuard('jwt'))
  @Get('profile')
  getProfile(@Request() req) {
    return req.user;
  }
}
  • auth.module.ts使用register配置JwtModule

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from '@/user/user.module';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { AuthController } from '@/auth/auth.controller';

@Module({
  imports: [
    UserModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: 3600 * 24 * 7 + 's' }, // 签名有效时间
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],// 抛出可以在其他模块中调用方法
})
export class AuthModule {}
  • jwt-auth.guard.ts文件设置路由访问全局守卫验证除白名单外的请求路径访问是否带有token如果有token验证用户是否存在

import {
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '@/auth/auth.service';
import { UserService } from '@/user/user.service';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from '@/app.module';
/**
 * @guard文件作用:守卫
 */

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor() {
    // private readonly userService: UserService, // private readonly authService: AuthService,
    super();
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    console.log('进入全局守卫');
    const req = context.switchToHttp().getRequest();
    const res = context.switchToHttp().getResponse();
    /**
     * @如果白名单数组中存在路径
     */
    if (this.hasUrl(this.urlList, req.url)) return true;

    try {
      // 获取token
      const accessToken = req.get('Authorization');
      if (!accessToken) throw new UnauthorizedException('请先登录');
      // 获取id
      // @ts-ignore
      const app = await NestFactory.create<NestExpressApplication>(AppModule, {
        logger: true,
      });
      const authService = app.get(AuthService);
      const userService = app.get(UserService);
      const user = await authService.verifyToken(accessToken);
      console.log(222222222, user);
      if (Object.keys(user).length > 0) {
        const resData = await userService.userfindOne(user.sub);
        if (resData.code === 200) return true;
      }
    } catch (e) {
      console.log(e);
      return false;
    }
  }
  // 白名单数组
  private urlList: string[] = ['/react-ant-admin/auth/login'];

  // 验证该次请求是否为白名单内的路由
  private hasUrl(urlList: string[], url: string): boolean {
    let flag = false;
    if (urlList.indexOf(url) !== -1) {
      flag = true;
    }
    console.log('11111', url, flag);
    return flag;
  }
}


  • main.ts中注册全局守卫
import { JwtAuthGuard } from '@/auth/jwt-auth.guard';
  // 全局守卫注册
  // @ts-ignore
  app.useGlobalGuards(new JwtAuthGuard());
  • 验证JWT是否生效

Xnip2022-09-28_14-53-16.jpg

Xnip2022-09-28_14-53-26.jpg

Xnip2022-09-28_15-15-35.jpg