refactor: 重构后端鉴权方式
This commit is contained in:
@@ -10,11 +10,11 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { LoginByPasswordDto } from './dto/login.dto';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { UserSessionService } from 'src/user/services/user-session.service';
|
||||
import { Throttle, ThrottlerGuard } from '@nestjs/throttler';
|
||||
import { Response } from 'express';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
import { AuthGuard } from './guards/auth.guard';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
@@ -40,8 +40,8 @@ export class AuthController {
|
||||
// }
|
||||
// }
|
||||
|
||||
private setUserToken(res: Response, token: string) {
|
||||
res.cookie('token', token, {
|
||||
private setUserSession(res: Response, sessionId: string) {
|
||||
res.cookie('session', sessionId, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
@@ -56,11 +56,10 @@ export class AuthController {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
const { identifier, password } = loginDto;
|
||||
const loginRes = await this.authService.loginWithPassword(identifier, password);
|
||||
const { userId, token } = loginRes;
|
||||
this.setUserToken(res, token);
|
||||
const session = await this.authService.loginWithPassword(identifier, password);
|
||||
this.setUserSession(res, session.sessionId);
|
||||
return {
|
||||
user: await this.userService.findById(userId),
|
||||
user: await this.userService.findById(session.userId),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@ export class AuthController {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@UseGuards(AuthGuard)
|
||||
@Post('logout')
|
||||
async logout(@Request() req) {
|
||||
const { userId, sessionId } = req.user;
|
||||
|
||||
@@ -2,35 +2,22 @@ import { forwardRef, Module } from '@nestjs/common';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UserModule } from 'src/user/user.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UserSession } from 'src/user/entities/user-session.entity';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { VerificationModule } from 'src/verification/verification.module';
|
||||
import { OptionalAuthGuard } from './strategies/OptionalAuthGuard';
|
||||
import { AuthGuard } from './guards/auth.guard';
|
||||
import { OptionalAuthGuard } from './guards/optional-auth.guard';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
forwardRef(() => UserModule),
|
||||
TypeOrmModule.forFeature([UserSession]),
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
secret: configService.get<string>('JWT_SECRET', 'tone-page'),
|
||||
signOptions: {
|
||||
expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1d'),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
VerificationModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy, OptionalAuthGuard],
|
||||
exports: [PassportModule, JwtStrategy, AuthService, OptionalAuthGuard],
|
||||
providers: [AuthService, AuthGuard, OptionalAuthGuard],
|
||||
exports: [AuthService, AuthGuard, OptionalAuthGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
export class AuthModule { }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createHash } from 'crypto';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
import { User } from 'src/user/entities/user.entity';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { UserSessionService } from 'src/user/services/user-session.service';
|
||||
import { VerificationService } from 'src/verification/verification.service';
|
||||
import { BusinessException } from 'src/common/exceptions/business.exception';
|
||||
@@ -12,7 +10,6 @@ import { ErrorCode } from 'src/common/constants/error-codes';
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly userSessionService: UserSessionService,
|
||||
private readonly verificationService: VerificationService,
|
||||
) { }
|
||||
@@ -49,11 +46,9 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
// 登录成功,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
userId: user.userId,
|
||||
};
|
||||
const { userId } = user;
|
||||
|
||||
return this.userSessionService.createSession(userId);
|
||||
}
|
||||
|
||||
async loginWithPhone(data: { phone: string; code: string; }) {
|
||||
@@ -93,9 +88,8 @@ export class AuthService {
|
||||
throw new BadRequestException('请求失败,请稍后再试');
|
||||
}
|
||||
|
||||
// 登录,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
userId: user.userId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,18 +97,4 @@ export class AuthService {
|
||||
return createHash('sha256').update(`${password}${salt}`).digest('hex');
|
||||
}
|
||||
|
||||
private async generateToken(user: User) {
|
||||
// 存储
|
||||
const sessionRes = await this.userSessionService.createSession(
|
||||
user.userId,
|
||||
);
|
||||
|
||||
const payload = {
|
||||
userId: user.userId,
|
||||
sessionId: sessionRes.sessionId,
|
||||
};
|
||||
|
||||
// 颁发token
|
||||
return this.jwtService.sign(payload);
|
||||
}
|
||||
}
|
||||
|
||||
38
apps/backend/src/auth/guards/auth.guard.ts
Normal file
38
apps/backend/src/auth/guards/auth.guard.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// auth.guard.ts
|
||||
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { UserSessionService } from 'src/user/services/user-session.service';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private userService: UserService,
|
||||
private userSessionService: UserSessionService,
|
||||
) { }
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
// 从 Cookie 读取 session
|
||||
const sessionId = request.cookies?.['session'];
|
||||
if (!sessionId) {
|
||||
throw new UnauthorizedException('登陆凭证无效,请重新登陆');
|
||||
}
|
||||
|
||||
// 验证 session
|
||||
const session = await this.userSessionService.getSession(sessionId);
|
||||
if (!session) {
|
||||
throw new UnauthorizedException('登陆凭证无效,请重新登陆');
|
||||
}
|
||||
|
||||
// 附加 user 到 req
|
||||
const user = await this.userService.findOne({ userId: session.userId });
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('用户不存在');
|
||||
}
|
||||
|
||||
(request as any).user = { ...user, sessionId };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
16
apps/backend/src/auth/guards/optional-auth.guard.ts
Normal file
16
apps/backend/src/auth/guards/optional-auth.guard.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ExecutionContext, Injectable } from "@nestjs/common";
|
||||
import { AuthGuard } from "./auth.guard";
|
||||
|
||||
@Injectable()
|
||||
export class OptionalAuthGuard extends AuthGuard {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
return await super.canActivate(context);
|
||||
} catch (error) {
|
||||
// 验证失败时,req.user = null,但允许继续
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request.user = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class OptionalAuthGuard extends AuthGuard('jwt') implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
await super.canActivate(context);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('OptionalAuthGuard error:', error);
|
||||
return true; // 如果验证失败,仍然允许访问
|
||||
}
|
||||
}
|
||||
|
||||
handleRequest<TUser = any>(
|
||||
err: any,
|
||||
user: any,
|
||||
// info: any,
|
||||
// context: ExecutionContext,
|
||||
// status?: any,
|
||||
): TUser {
|
||||
if (err || !user) {
|
||||
return null; // 如果没有用户信息,返回null
|
||||
}
|
||||
return user; // 如果有用户信息,返回用户对象
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { UserSessionService } from 'src/user/services/user-session.service';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly userSessionService: UserSessionService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET', 'tone-page'),
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
const { userId, sessionId } = payload ?? {};
|
||||
|
||||
await this.userSessionService.isSessionValid(
|
||||
userId,
|
||||
sessionId,
|
||||
).catch((e) => {
|
||||
throw new UnauthorizedException(`${e}`);
|
||||
});
|
||||
|
||||
const user = await this.userService.findOne({ userId });
|
||||
if (!user) {
|
||||
throw new BadRequestException('用户不存在');
|
||||
}
|
||||
|
||||
return {
|
||||
...user,
|
||||
sessionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user