refactor: 重构后端鉴权方式
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user