可以使用已经存在的
controller
、service
、module
也可以新建,后续文章使用的都是新建的文件,整个过程就不使用dto
和entities
了
1. 不管三七二十一,先使用命令创建controller
、service
、module
- 创建
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
中增加login
和profile
两个方法以及引入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
中增加login
和profile
两个方法
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.ts
的login
方法中增加登录校验逻辑,并且生成 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
,并在里面写入相关内容
- 使用命令创建
npx nest g gu auth
- 在
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
生效
- 使用
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 生效了
- 使用全局配置的方式(个人比较推荐)
更新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.ts
和 auth.guard.ts
中将 secret
和 expiresIn
更新以下即可。