refactor: 重构后端鉴权方式

This commit is contained in:
2025-12-17 15:33:25 +08:00
parent fdc8da2308
commit 8c2a50127a
18 changed files with 97 additions and 264 deletions

View File

@@ -19,13 +19,13 @@ import { RemoveUserDto } from '../dto/admin-user/remove.dto';
import { RolesGuard } from 'src/common/guard/roles.guard';
import { Roles } from 'src/common/decorators/role.decorator';
import { Role } from 'src/auth/role.enum';
import { AuthGuard } from '@nestjs/passport';
import { AuthGuard } from 'src/auth/guards/auth.guard';
@Controller('admin/user')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin)
export class AdminUserController {
constructor(private readonly userService: UserService) {}
constructor(private readonly userService: UserService) { }
@Get()
async list(@Query() listDto: ListDto) {

View File

@@ -9,20 +9,20 @@ import {
Put,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CreateBlogDto } from 'src/admin/dto/admin-web/create-blog.dto';
import { SetBlogPasswordDto } from 'src/admin/dto/admin-web/set-blog-password.dto';
import { UpdateBlogDto } from 'src/admin/dto/admin-web/update-blog.dto';
import { AuthGuard } from 'src/auth/guards/auth.guard';
import { Role } from 'src/auth/role.enum';
import { BlogService } from 'src/blog/blog.service';
import { Roles } from 'src/common/decorators/role.decorator';
import { RolesGuard } from 'src/common/guard/roles.guard';
@Controller('/admin/web/blog')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin)
export class AdminWebBlogController {
constructor(private readonly adminWebBlogService: BlogService) {}
constructor(private readonly adminWebBlogService: BlogService) { }
@Get()
async list() {

View File

@@ -9,18 +9,18 @@ import {
Put,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CreateResourceDto } from 'src/admin/dto/admin-web/create-resource.dto';
import { AuthGuard } from 'src/auth/guards/auth.guard';
import { Role } from 'src/auth/role.enum';
import { Roles } from 'src/common/decorators/role.decorator';
import { RolesGuard } from 'src/common/guard/roles.guard';
import { ResourceService } from 'src/resource/resource.service';
@Controller('/admin/web/resource')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin)
export class AdminWebResourceController {
constructor(private readonly resourceService: ResourceService) {}
constructor(private readonly resourceService: ResourceService) { }
@Get()
async list() {

View File

@@ -7,7 +7,6 @@ import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { VerificationModule } from './verification/verification.module';
import { NotificationModule } from './notification/notification.module';
import { PassportModule } from '@nestjs/passport';
import { ResourceModule } from './resource/resource.module';
import { BlogModule } from './blog/blog.module';
import { AdminModule } from './admin/admin.module';
@@ -28,7 +27,6 @@ import { ThrottlerModule } from '@nestjs/throttler';
entities: [],
synchronize: process.env.NODE_ENV !== 'production', // Set to false in production
}),
PassportModule.register({ defaultStrategy: 'jwt' }),
ThrottlerModule.forRoot({
throttlers: [
{

View File

@@ -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;

View File

@@ -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 { }

View File

@@ -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);
}
}

View 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;
}
}

View 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;
}
}
}

View File

@@ -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; // 如果有用户信息,返回用户对象
}
}

View File

@@ -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,
};
}
}

View File

@@ -11,11 +11,11 @@ import {
UseGuards,
} from '@nestjs/common';
import { BlogService } from './blog.service';
import { OptionalAuthGuard } from 'src/auth/strategies/OptionalAuthGuard';
import { UserService } from 'src/user/user.service';
import { createBlogCommentDto } from './dto/create.blogcomment.dto';
import { Throttle, ThrottlerGuard } from '@nestjs/throttler';
import { BlogPermission } from './blog.permission.enum';
import { OptionalAuthGuard } from 'src/auth/guards/optional-auth.guard';
@Controller('blog')
export class BlogController {

View File

@@ -1,12 +1,12 @@
import { Controller, Get, Request, UseGuards } from '@nestjs/common';
import { OssService } from './oss.service';
import { AuthGuard } from '@nestjs/passport';
import { AuthGuard } from 'src/auth/guards/auth.guard';
@Controller('oss')
export class OssController {
constructor(private readonly ossService: OssService) {}
constructor(private readonly ossService: OssService) { }
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard)
@Get('sts')
async getStsToken(@Request() req) {
const { userId } = req.user;

View File

@@ -1,9 +1,12 @@
import { Module } from '@nestjs/common';
import { OssService } from './oss.service';
import { OssController } from './oss.controller';
import { AuthModule } from 'src/auth/auth.module';
import { UserModule } from 'src/user/user.module';
@Module({
providers: [OssService],
controllers: [OssController],
imports: [AuthModule, UserModule],
})
export class OssModule {}
export class OssModule { }

View File

@@ -17,32 +17,19 @@ export class UserSessionService {
return this.userSessionRepository.save(session);
}
/**
* @throws string 无效原因
*/
async isSessionValid(userId: string, sessionId: string): Promise<void> {
async getSession(sessionId: string) {
const session = await this.userSessionRepository.findOne({
where: {
userId,
sessionId,
},
withDeleted: true,
});
if (session === null) {
throw '登陆凭证无效';
}
if (session.deletedAt !== null) {
throw session.disabledReason || '登陆凭证无效';
}
return null;
return session;
}
async invalidateSession(userId: string, sessionId: string, reason?: string): Promise<void> {
async invalidateSession(sessionId: string, reason?: string): Promise<void> {
await this.userSessionRepository.update(
{ userId, sessionId, deletedAt: null },
{ sessionId, deletedAt: null },
{
deletedAt: new Date(),
disabledReason: reason || null,

View File

@@ -1,24 +1,24 @@
import { Body, Controller, Get, Put, Request, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { AuthGuard } from '@nestjs/passport';
import { UpdateUserPasswordDto } from './dto/update-user-password.dto';
import { AuthService } from 'src/auth/auth.service';
import { AuthGuard } from 'src/auth/guards/auth.guard';
@Controller('user')
export class UserController {
constructor(
private readonly userService: UserService,
private readonly authService: AuthService,
) {}
) { }
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard)
@Get('me')
async getMe(@Request() req) {
const { user } = req;
return this.userService.findOne({ userId: user.userId });
return this.userService.findById(user.userId);
}
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard)
@Put('password')
async update(@Request() req, @Body() dto: UpdateUserPasswordDto) {
return this.userService.setPassword(req.user.userId, dto.password);