1、前期准备工作
a、安装数据库,mysql, 配置数据库连接参看四
b、新建User模块,用于管理User
实现注册:
@Controller('user')
export class UserController {
public constructor(
private readonly userService: UserService,
) {}
@Post('/register')
public async register(@Body() body: any) {
return await this.userService.registerUser(body);
}
}
userService实现简易版(注意,这里用的是bcrypt来实现对密码进行加密的,具体参看nest官方文档)
@Injectable()
export class UserService {
public constructor(@InjectRepository(User) private readonly user: Repository<User>) {}
public async registerUser(info: { name: string; nickname: string; password: string }) {
const { name, nickname, password } = info;
const user = await this.user.findOne({ where: { name } });
if (!user) {
const saltOrRounds = 10;
const pwd = await bcrypt.hash(password, saltOrRounds);
await this.user
.createQueryBuilder()
.insert()
.into(User)
.values([
{
name,
nickName: nickname,
password: pwd,
createTime: Date.now() / 1000,
updateTime: Date.now() / 1000,
},
])
.execute();
return {
code: 0,
message: 'ok',
};
}
return {
code: 1,
message: '用户已存在',
};
}
}
这样就已实现了简易版的注册
2、jwt的配置与验证
a、安装依赖
yarn add @nestjs/passport passport passport-jwt @nestjs/jwt -S
yarn add @types/passport-jwt -D
b、创建 Auth 模块
nest g module /routers/auth // 创建auth模块
nest g service /routers/auth // 创建auth服务
c. 新建一个存储常量的文件
在 auth
文件夹下新增一个 constants.ts
,用于存储各种用到的常量:
export const jwtSalt = '.even_jwt_@@__';
d、编写 JWT 策略
在 auth
文件夹下新增一个 jwt.strategy.ts
,用于编写 JWT 的验证策略:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtSalt } from './constants';
import * as crypto from 'crypto-js';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtSalt,
});
}
// JWT验证 - Step 4: 被守卫调用
async validate(payload: any) {
console.log(`JWT验证 - Step 4: 被守卫调用`);
const info = payload.info;
// const userInfo = crypto.AES.decrypt(info, 'salt').toString(crypto.enc.Utf8);
// console.log(JSON.parse(userInfo));
return {
info,
};
}
}
注意:为了防止jwt被编译出来,所以采用了加密方式, 注释掉的部份只是为了验证,可以不编写,如果有其他需求的话按该方式进行解码
注意:return的信息会被放在request里的user部份,可以通过 ctx.switchToHttp().getRequest().user进行获取,可以编写成装饰器如下
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
/**
* 获取user的信息
*/
export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
e、编写auth.service.ts的验证逻辑
import { User } from '@app/entities/user.entity';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import * as crypto from 'crypto-js';
@Injectable()
export class AuthService {
public constructor(
@InjectRepository(User) private user: Repository<User>,
private readonly jwtService: JwtService,
) {}
// JWT验证 - Step 2: 校验用户信息
public async validateUser(name: string, pwd: string): Promise<boolean> {
const user = await this.user.findOne({
select: ['name', 'password'],
where: {
name,
},
});
return user && bcrypt.compare(pwd, user.password);
}
// JWT验证 - Step 3: 处理 jwt 签证
public certificate(user: any): string {
// 这里对jwt的内容采用了 crypto 中的aes的对称加密方法
const payload = {
info: crypto.AES.encrypt(
JSON.stringify({ name: user.name, password: user.password }),
'salt',
).toString(),
};
return this.jwtService.sign(payload);
}
}
f、关联jwt策略与service到Auth.module模块中
import { JwtStrategy } from './jwt.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { User } from '@app/entities/user.entity';
import { jwtSalt } from './constants';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: jwtSalt,
signOptions: { expiresIn: '30m' }, // token 过期时效
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService], // 因为auth模块需要导入到User模块,所以需要配置导出
})
export class AuthModule {}
g、在user模块中需要导入AuthModule, 因为user模块需要用到签发证书以及用户验证的方法
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from '@app/entities/user.entity';
import { AuthModule } from '../auth/auth.module';
@Module({
imports: [TypeOrmModule.forFeature([User]), AuthModule],
providers: [UserService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule {}
h、在UserController中添加登录的方法
import { AuthService } from './../auth/auth.service';
import { UserService } from './user.service';
import { Body, Controller, Post } from '@nestjs/common';
@Controller('user')
export class UserController {
public constructor(
private readonly userService: UserService,
private readonly authService: AuthService,
) {}
@Post('/register')
public async register(@Body() body: any) {
return await this.userService.registerUser(body);
}
@Post('/login')
public async login(@Body() body: any) {
const { name, nickname, password } = body;
const result = await this.authService.validateUser(name, password);
if (result) {
return {
code: 0,
message: 'ok',
token: this.authService.certificate({ name, password }), // 签发token
};
}
return {
code: 1,
message: 'fail',
};
}
}
i、在指定路径上添加jwt验证
@UseGuards(AuthGuard('jwt')) // 使用 'JWT' 进行验证
@Get()
public async init() {
const result = await this.cryptoService.cryptoPassword();
return {
code: 0,
token: result,
};
}
这个时候访问已需要验证的路由,那么就需要在header中添加 字段 Authorization
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbmZvIjoiVTJGc2RHVmtYMStpVW1NMXBzWm13b3YyUS9QSHBTY3lqMzBTOVFpb1pDZW
xPdHRvSGo2Rm9wZFZXSnFoNko4M3hPb2NsK1QzanNIS0NuaXNRUkVnckE9PSIsImlhdCI6MTY1MzE0OTYwMiwiZXhwIjoxNjUzMTUxNDAyfQ.vp62XIxwdynol4aql7
XgYE4-Xpq1PcpaTUzLbd5UHyU
至于redis的使用,可以用ioredis这个库,至于登录后如何刷新,可以参看yii-redis那篇随笔