本次完整实践nestjs的注册登陆,到jwt授权认证,到guard守卫拦截验证,到strategy的JWT策略。
注册登录controller
// auth.controller.ts
import { Body, Controller, Get, Post, UseGuards } from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import { AuthService } from "@libs/common/auth/auth.service";
import { LoginDto } from "@libs/common/auth/auth.dto";
@Controller("auth")
@ApiTags("认证中心")
export class AuthController {
constructor(private authService: AuthService) {
}
@Post("login")
@ApiOperation({ summary: "登录" })
async login(@Body() body: LoginDto) {
return this.authService.validateUser(body.username, body.password);
}
@Post("register")
@ApiOperation({ summary: "注册" })
async register(@Body() body: LoginDto) {
await this.authService.register(body);
return this.login(body);
}
}
注册登陆及JWT的service实现
// auth.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { User } from "@libs/db/schemas/user.schema";
import { ReturnModelType } from "@typegoose/typegoose";
import { compareSync } from "bcryptjs";
import { ConfigService } from "@nestjs/config";
import { LoginDto } from "@libs/common/auth/auth.dto";
@Injectable()
export class AuthService {
constructor(
@Inject(User.name) private readonly userModel: ReturnModelType<typeof User>,
private jwtService: JwtService,
private config: ConfigService
) {
}
async validateUser(username: string, password: string) {
const user = await this.userModel.findOne({ username }).select("+password");
if (!user) {
throw new HttpException("用户不存在", HttpStatus.BAD_REQUEST);
}
if (!compareSync(password, user.password)) {
throw new HttpException("密码不正确", HttpStatus.BAD_REQUEST);
}
const { accessToken, refreshToken } = this.genToken({ data: user._id });
return {
accessToken,
refreshToken,
id: user.id,
username: user.username,
avatar: user.avatar
};
}
async register(body: LoginDto) {
const { username } = body;
const user = await this.userModel.findOne({ username });
if (user) {
throw new HttpException("用户已存在", HttpStatus.BAD_REQUEST);
}
return this.userModel.create(body);
}
async findUserById(id: string) {
const user = await this.userModel.findById(id);
if (!user) {
throw new HttpException("用户不存在", HttpStatus.BAD_REQUEST);
} else {
return user;
}
}
genToken(payload: any) {
const accessToken = `Bearer ${this.jwtService.sign(payload, {
secret: this.config.get("JWT_SECRET"),
expiresIn: "30m"
})}`;
const refreshToken = this.jwtService.sign(payload, {
secret: this.config.get("JWT_SECRET"),
expiresIn: "2h"
});
return { accessToken, refreshToken };
}
verifyToken(token: string) {
try {
if (!token) return null;
const { data } = this.jwtService.verify(token, { secret: this.config.get("JWT_SECRET") });
return data;
} catch (error) {
return null;
}
}
}
守卫实现,拦截accessToken和refreshToken,过期进行续签。
// jwt-auth.guard.ts
import { AuthGuard } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthService } from "@libs/common/auth/auth.service";
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(private readonly authService: AuthService) {
super();
}
async canActivate(context) {
const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
const reqAccessToken = req.get("Authorization").split(" ")[1];
if (!reqAccessToken) throw new UnauthorizedException("请先登录");
const atUserId = this.authService.verifyToken(reqAccessToken);
if (atUserId) return this.activate(context);
const reqRefreshToken = req.get("RefreshToken");
const rtUserId = this.authService.verifyToken(reqRefreshToken);
if (!rtUserId) throw new UnauthorizedException("当前登录已过期,请重新登录");
if (await this.authService.findUserById(rtUserId)) {
const { accessToken, refreshToken } = this.authService.genToken({ data: rtUserId });
req.headers["authorization"] = accessToken;
req.headers["refreshtoken"] = refreshToken;
// 在响应头中加入新的token,客户端判断响应头有无 Authorization 字段,有则重置
res.header("Authorization", accessToken);
res.header("RefreshToken", refreshToken);
// 将当前请求交给下一级
return this.activate(context);
}
}
async activate(context) {
return super.canActivate(context) as Promise<boolean>;
}
}
JWT解析后进行校验
// jwt.strategy.ts
import { ExtractJwt, Strategy, StrategyOptions } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Inject } from "@nestjs/common";
import { ReturnModelType } from "@typegoose/typegoose";
import { User } from "@libs/db/schemas/user.schema";
import { ConfigService } from "@nestjs/config";
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@Inject(User.name) private readonly userModel: ReturnModelType<typeof User>,
configService: ConfigService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get("JWT_SECRET")
} as StrategyOptions);
}
async validate(payload) {
return this.userModel.findById(payload.data);
}
}