重生之我在NestJS中使用jwt鉴权

可以使用已经存在的controllerservicemodule也可以新建,后续文章使用的都是新建的文件,整个过程就不使用dtoentities

1. 不管三七二十一,先使用命令创建controllerservicemodule

  • 创建controller: npx nest g co auth --no-spec
  • 创建service: npx nest g s auth --no-spec
  • 创建module: npx nest g mo auth --no-spec

**注意:**以上创建的文件会自动在app.module.ts中更新响应的配置,如果不需要自动导入,请在后面增加--skip-import

2. 在刚刚创建的auth/auth.controller.ts中增加loginprofile两个方法以及引入AuthService

typescript 复制代码
import { Body, Controller, Get, Post } from '@nestjs/common'
import { AuthService } from './auth.service'

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  login(@Body() params: any) {
    return this.authService.login(params)
  }

  @Get('profile')
  profile() {
    return this.authService.profile()
  }
}

3. 在auth/auth.service.ts中增加loginprofile两个方法

typescript 复制代码
import { Injectable } from '@nestjs/common'

@Injectable()
export class AuthService {
  private readonly users = [
    { id: 1, username: 'admin', password: '123456' },
    { id: 2, username: 'guest', password: '000000' }
  ]
  login(params: any) {
    console.log(params)
  }

  profile() {
    return 'profile'
  }
}

到这里,可以通过地址访问到创建的控制里面的方法了,比如访问:http://localhost:3000/auth/profile 可以得到一个 profile 字符串,接下来就是引入 jwt 了。

4. 引入 jwt 的依赖

  • 安装依赖: npm install @nestjs/jwt -S

5. 需要在刚刚创建的auth/auth.module.ts中引入 jwt 相关的配置

typescript 复制代码
import { Module } from '@nestjs/common'
import { JwtModule } from '@nestjs/jwt'

@Module({
  imports: [
    JwtModule.register({
      global: true, // 表示是否全局启用
      secret: '123456', // 密钥,先随便写死,后面再改
      signOptions: {
        // 这是配置项,有很多,可以点击去看 d.ts 的介绍
        expiresIn: '4h' // 过期时间,单位秒,4小时后过期,
      }
    })
  ]
})
export class AuthModule {}

这个配置里面比较重要的,基本上就是 secret 了,后面会写入到配置文件中,再引入进来,比较灵活的控制了。

6. 在auth/auth.service.tslogin方法中增加登录校验逻辑,并且生成 jwt 的 token 返回给前端,完整代码如下

需要引入一下JwtService这个服务

typescript 复制代码
import { Injectable } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'

@Injectable()
export class AuthService {
  private readonly users = [
    { id: 1, username: 'admin', password: '123456' },
    { id: 2, username: 'guest', password: '000000' }
  ]

  // 引入 JwtService 服务,用于生成 token 和验证 token 的有效性
  constructor(private readonly jwtService: JwtService) {}

  login(params: any) {
    const { username, password } = params
    const user = this.users.find(user => user.username === username)
    console.log(params)
    if (user && user.password === password) {
      // 生成jwt token
      const token = this.jwtService.sign({
        sub: user.id,
        username: user.username
      })
      return {
        code: 200,
        data: { token },
        msg: '登录成功'
      }
    }

    // 直接返回账号密码错误信息
    return {
      code: -1,
      msg: '账号或密码错误'
    }
  }

  profile() {
    return 'profile'
  }
}

启动服务,使用命令测试一下请求: curl -X POST http://localhost:3000/auth/login -d '{"username": "admin", "password": "123456"}' -H "Content-Type: application/json"

到现在为止,我们已经成功的生成了一个 token,接下来就是如何让这个 token 在接口中生效的问题,那么需要创建一个 guard。

7. 使用命令创建auth.guard.ts,并在里面写入相关内容

  1. 使用命令创建 npx nest g gu auth
  2. auth/auth.guard.ts中写入鉴权相关的内容,代码如下
typescript 复制代码
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { Reflector } from '@nestjs/core'
import { Observable } from 'rxjs'
import { Request } from 'express'
import { jwtSecretConstant } from 'src/config/jwt'

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService, private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    // 这个可以先不看,和后面使用注解有关系
    const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [context.getHandler(), context.getClass()])

    if (isPublic) return true

    const request =  context.switchToHttp().getRequest()
    // 主要是这里解析header中的 Authorization 这个值
    const token = this.extractTokenFromHeader(request)
    if (!token) {
      throw new UnauthorizedException('Token not found')
    }
    return this.jwtService
      .verifyAsync(token, {
        secret: '123456' // 这里和之前的auth.module.ts中保持一致
      })
      .then(payload => {
        request['user'] = payload // 这一步可以不要,主要是让用户在controller中可以获取一下用户信息之类的

        return true
      })
      .catch(() => {
        throw new UnauthorizedException('Invalid token')
      })
  }

  extractTokenFromHeader(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') || []
    return type === 'Bearer' ? token : undefined
  }
}

现在已经创建好了 guard,该如何使用呢,有两种方式:① 使用注解的方式;② 使用providers配置全局的

8.如何让刚刚创建的auth.guard.ts生效

  1. 使用UseGuards注解的方式

auth/auth.controller.ts中的profile方法上面添加注解,完整代码如下

typescript 复制代码
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'
import { AuthService } from './auth.service'
import { AuthGuard } from './auth.guard'

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  login(@Body() params: any) {
    return this.authService.login(params)
  }

  @Get('profile')
  @UseGuards(AuthGuard) // 主要使用这个注解,需要引入一下
  profile() {
    return this.authService.profile()
  }
}

现在已经使用起来了,我们通过命令或者浏览器访问一下: http://localhost:3000/auth/profile 这地址,会发现访问不了了,返回了 401 ,说明我们没有权限访问;我们可以在 headers 中携带之前生成的 token 再次访问试一下,命令访问方式:curl -X GET http://localhost:3000/auth/profile -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE3MjEyODg3OTIsImV4cCI6MTcyMTMwMzE5Mn0.D5d8gEUtadfBM7afE5tj_hmYkG-6N2_Hf8IyKe45RBY',此时能正常返回 profile 了,说明 jwt 生效了

  1. 使用全局配置的方式(个人比较推荐)

更新auth/auth.module.ts的配置文件,完整代码如下

typescript 复制代码
import { Module } from '@nestjs/common'
import { APP_GUARD } from '@nestjs/core'
import { JwtModule } from '@nestjs/jwt'
import { AuthGuard } from './auth.guard'

@Module({
  imports: [
    JwtModule.register({
      global: true, // 表示是否全局启用
      secret: '123456', // 密钥,先随便写死,后面再改
      signOptions: {
        // 这是配置项,有很多,可以点击去看 d.ts 的介绍
        expiresIn: '4h' // 过期时间,单位秒,4小时后过期,
      }
    })
  ],
  providers: [{ provide: APP_GUARD, useClass: AuthGuard }] // 主要是添加这一行
})
export class AuthModule {}

可以把 AuthController 里面的 profile 上面的 UseGuard 注解删除试一下,和刚刚的效果是一模一样的;到这个地方,所有控制器的所有的方法都需要传入 Authorization 这个 header 头,显然是不太合理的。比如刚刚的登录方法,是不需要鉴权的,所以需要排除一下,可以创建一个全局的注解,之前在auth/auth.guard.ts中对注解的判断就有效了。

9. 创建全局注解,排除不需要鉴权的方法

  • 使用命令创建nest g d auth,会在 src 根目录创建一个auth.decorator.ts的文件,删除里面的内容,写入以下内容
typescript 复制代码
import { SetMetadata } from '@nestjs/common'

// 创建一个注解,外边可以用 @Public() 使用它
export const Public = () => SetMetadata('isPublic', true)

注解创建完成了,我们到auth/auth.controller.ts中的login方法上面添加一行: @Public(),添加的时候需要引入一下import { Public } from './decorators/public.decorator';;现在我们方法登录的这个方法,就不会进行权限校验了。到此,jwt 鉴权的流程基本上就结束了,前面使用的secret可以弄成全局的配置文件

10. 将 jwt 用到的 secret 弄成全局的配置文件

在 src 下面创建一个 config 目录,再在 config 下面创建一个jwt.ts文件,写入以下内容

typescript 复制代码
export const jwtSecretConstant = '123456' // 这个是 secret
export const jwtExpireConstant = '5d' // 这个是过期时间

创建好了之后,直接去 auth.module.tsauth.guard.ts 中将 secretexpiresIn 更新以下即可。

相关推荐
醉の虾14 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧22 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm32 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep70144 分钟前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王1 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒1 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript
gqkmiss2 小时前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃2 小时前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰2 小时前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter