登录和注册(代码示例)
需要安装的依赖
bash
pnpm add @nestjs/jwt @nestjs/throttler helmet
auth.service.ts
ts
import {
ConflictException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
import { SignUpDto } from './dto/sign-up.dto';
import { SignInDto } from './dto/sign-in.dto';
import { NotFoundError } from '@mikro-orm/core';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async generateToken(user: any): Promise<{ access_token: string }> {
const payload = { email: user.email, sub: user.id };
return {
access_token: await this.jwtService.signAsync(payload),
};
}
async signUp(signUpDto: SignUpDto): Promise<any> {
const { email, name, password } = signUpDto;
const user = await this.usersService.findOne(email);
if (user) {
throw new ConflictException('User already exists');
}
const hashedPassword = await bcrypt.hash(password, 10);
await this.usersService.create({ email, name, password: hashedPassword });
return await this.usersService.findOne(email);
}
async signIn(signInDto: SignInDto): Promise<any> {
const { email, password } = signInDto;
const user = await this.usersService.findOne(email);
if (!user) {
throw new NotFoundError('User not found');
}
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid password');
}
const token = await this.generateToken(user);
return { user, token };
}
}
auth.controller.ts
ts
import { Controller } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Post, Body } from '@nestjs/common';
import { SignUpDto } from './dto/sign-up.dto';
import { SignInDto } from './dto/sign-in.dto';
import { Public } from './decorator/public.decorator';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
@Public()
async signUp(@Body() signUpDto: SignUpDto): Promise<any> {
return this.authService.signUp(signUpDto);
}
@Post('login')
@Public()
async signIn(@Body() signInDto: SignInDto): Promise<any> {
return this.authService.signIn(signInDto);
}
}
auth.module.ts
ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtModule } from '@nestjs/jwt';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
@Module({
imports: [
UsersModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h', algorithm: 'HS256' },
}),
],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
controllers: [AuthController],
})
export class AuthModule {}
JWT
auth.guard.ts
bash
nest g guard auth/auth --no-spec --flat
ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from './decorator/public.decorator';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private reflector: Reflector,
) {}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
]);
if (isPublic) {
// 💡 See this condition
return true;
}
// get the request object from the execution context
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('No token provided');
}
// verify the token using the JwtService
try {
const payload = await this.jwtService.verifyAsync(token);
request['user'] = payload;
} catch (error) {
throw new UnauthorizedException('Invalid token');
}
return true;
}
}
public.decorator.ts
bash
nest g decorator auth/decorator/public --flat
ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
限流
app.module.ts
ts
import { ThrottlerModule } from '@nestjs/throttler';
import throttlerConfig from './common/config/c';
ThrottlerModule.forRoot(throttlerConfig)
throttler.config.ts
export default [
{
name: 'short',
ttl: 1000,
limit: 3,
},
{
name: 'medium',
ttl: 10000,
limit: 20,
},
{
name: 'long',
ttl: 60000,
limit: 100,
},
];
其他
helmet
main.ts
ts
// 加入 Helmet 中间件以增强安全性
app.use(helmet());