登录密码
上一章可以看到新增用户成功后遗留的问题
- 返回的数据中有密码
- 密码是明文没有加密
话不多说上代码
安装密码加密、密码验证及jwt相关依赖
yarn add bcryptjs @nestjs/jwt passport @nestjs/passport passport-jwt passport-local
yarn add @types/passport @types/passport-jwt @types/passport-local -D
user/entities/user.entity.ts
添加
...
+ import * as bcrypt from 'bcryptjs';
password: {
type: String,
required: [true, '请输入密码'],
minlength: [6, '密码最小长度6个字符'],
+ select: false,
},
+ // 密码加密
+ userSchema.pre('save', async function (next) {
+ if (!this.isModified('password')) {
+ next();
+ }
+ this.password = await bcrypt.hashSync(this.password);
这样查询方法就不会查出密码了
新增auth模块
专门用来处理登录的验证
nest g res auth
选择REST API 回车
密码验证这里使用了@nestjs/passport的策略方法 新增 auth/local.strategy.ts
import { compareSync } from 'bcryptjs';
import { PassportStrategy } from '@nestjs/passport';
import { IStrategyOptions, Strategy } from 'passport-local';
import { UserType } from 'src/user/entities/user.entity';
import { HttpException, HttpStatus } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
export class LocalStorage extends PassportStrategy(Strategy) {
constructor(
@InjectModel('Users') private readonly userModel: Model<UserType>,
) {
super({
usernameField: 'username',
passwordField: 'password',
} as IStrategyOptions);
}
async validate(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);
}
return user;
}
}
auth.module.ts
注入密码校验策略及表实体
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
+ import { LocalStorage } from './local.strategy';
+ import { UserService } from '../user/user.service';
+ import { MongooseModule } from '@nestjs/mongoose';
+ import userSchema from '../user/entities/user.entity'
@Module({
+ imports: [MongooseModule.forFeature([{ name: 'Users', schema: userSchema }])],
controllers: [AuthController],
+ providers: [AuthService,UserService, LocalStorage],
})
export class AuthModule {}
新增验证守卫
common下创建守卫文件夹并指令创建local-auth.guard.ts文件
nest g guard common/guard/local-auth
添加如下代码
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
将此守卫添加至auth.controller的登录方法中并使用守卫
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateAuthDto } from './dto/create-auth.dto';
+ import { LocalAuthGuard } from '../common/guard/local-auth/local-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
+ @Post('login')
+ @UseGuards(LocalAuthGuard)
create(@Body() createAuthDto: CreateAuthDto) {
+ return this.authService.login(createAuthDto);
}
}
此时登录密码验证就完成了。
注:新增需要自行排除返回的数据password字段
接下来添加jwt验证
JWT
首先先添加username查询方法 user/user.service.ts
...
async findByUsername(username: string) {
return this.userModel.findOne({
username,
});
}
...
auth/auth.module.ts 导入jwt模块
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { LocalStorage } from './local.strategy';
import { UserService } from '../user/user.service';
+ import { JwtModule } from '@nestjs/jwt';
+ import { ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import userSchema from '../user/entities/user.entity';
+ const jwtModule = JwtModule.registerAsync({
+ inject: [ConfigService],
+ useFactory: async (configService: ConfigService) => {
+ return {
+ secret: configService.get('SECRET', 'my-chat-app'),
+ signOptions: {
+ expiresIn: '4h',
+ },
+ };
+ },
+ });
@Module({
+ imports: [MongooseModule.forFeature([{ name: 'Users', schema: userSchema }]), jwtModule],
controllers: [AuthController],
providers: [AuthService, UserService, LocalStorage],
})
export class AuthModule {}
auth/auth.service.ts 添加jwt生成及解密方法
...
constructor(
private readonly userService: UserService,
+ private jwtService: JwtService,
) {}
+ createToken(user: Partial<User>) {
+ return this.jwtService.sign(user);
+ }
+ async decodeToken(token: string) {
+ return this.jwtService.decode(token.replace('Bearer ', ''));
+ }
async login(user: Partial<CreateAuthDto>) {
const userInfo = await this.userService.findByUsername(user.username);
+ const token = this.createToken({
+ _id: userInfo.id,
+ username: userInfo.username,
});
// if(userInfo.status !== 1){
// throw new HttpException('账号已被禁用',500)
// }
+ return { token, user: userInfo };
}
}
...
测试下token获取成功
验证token
在auth模块下同样创建jwt.strategy.ts jwt验证策略 策略代码如下
import { ConfigService } from '@nestjs/config';
import { UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { StrategyOptions, Strategy, ExtractJwt } from 'passport-jwt';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { UserType } from '../user/entities/user.entity';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectModel('Users') private readonly userModel: Model<UserType>,
private readonly configService: ConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get('SECRET'),
passReqToCallback: true,
ignoreExpiration: false,
} as StrategyOptions);
}
async validate(req: Request, payload: User) {
const existUser = await this.userModel.findOne({
_id: payload._id,
});
if (!existUser) {
throw new UnauthorizedException('token不正确');
}
return existUser;
}
}
将策略注入到user.module中
...
+ import { JwtStrategy } from 'src/auth/jwt.strategy';
...
- providers: [UserService],
+ providers: [UserService,JwtStrategy],
...
同样创建jwt验证守卫
nest g guard common/guard/jwt-auth
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from 'src/common/decorator/public.decorator'; // 需创建此装饰器
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
}
可以看到上边守卫代码中添加了不需要jwt验证的装饰器所以需要自己创建一个装饰器
nest g decorator common/decorator/public
代码很简单就是设置一个属性方便控制器使用
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
别忘了在app.module中使用刚刚创建的jwt-auth守卫
...
+ import { JwtAuthGuard } from './common/guard/jwt-auth/jwt-auth.guard';
...
providers: [
AppService,
+ {
+ provide: APP_GUARD,
+ useClass: JwtAuthGuard,
+ },
{
provide:APP_FILTER,
useClass: HttpExceptionFilter
},{
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor
}
],
...
测试一下可以看到所有的接口都因为没有token而返回401
因为某些接口可能不需要登录就可以访问所以,刚刚设置的装饰器就用上了
将登录和注册的方法加上这个装饰器
如:
auth/auth.controller.ts
...
+ import { Public } from '../common/decorator/public/public.decorator';
...
@Post('login')
+ @Public()
@UseGuards(LocalAuthGuard)
create(@Body() createAuthDto: CreateAuthDto) {
return this.authService.login(createAuthDto);
}
这样jwt的验证就完成了,这样项目的雏形就差不多了
注:数据库字符长度限制请视情况自行设置这里就不写出来了
接下来该业务代码