issue #39
All checks were successful
Test CI / build (push) Successful in 1m29s

- Access 토큰 만료 시 Refresh 토큰으로 Access 토큰 갱신 로직 구현 중
This commit is contained in:
2025-12-01 22:36:01 +09:00
parent 56cee12c81
commit 58d092536e
6 changed files with 96 additions and 11 deletions

View File

@@ -6,7 +6,7 @@ export class AuthService {
constructor(private readonly jwtService: JwtService) {} constructor(private readonly jwtService: JwtService) {}
generateTokens(payload: any) { generateTokens(payload: any) {
const accessToken = this.jwtService.sign(payload, { expiresIn: '1h' }); const accessToken = this.jwtService.sign(payload, { expiresIn: '5s' });
const refreshToken = this.jwtService.sign({id: payload.id}, { expiresIn: '7d' }); const refreshToken = this.jwtService.sign({id: payload.id}, { expiresIn: '7d' });
return { accessToken, refreshToken }; return { accessToken, refreshToken };

View File

@@ -1,5 +1,45 @@
import { Injectable } from "@nestjs/common"; import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { TokenExpiredError } from "@nestjs/jwt";
import { AuthGuard } from "@nestjs/passport"; import { AuthGuard } from "@nestjs/passport";
import { IS_PUBLIC_KEY } from "src/common/decorators/public.decorator";
@Injectable() @Injectable()
export class JwtAccessAuthGuard extends AuthGuard('access-token') {} export class JwtAccessAuthGuard extends AuthGuard('access-token') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass()
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
handleRequest(err: any, user:any, info:any) {
if (info instanceof TokenExpiredError) {
throw new UnauthorizedException({
statusCode: 401,
message: 'Access Token Expired',
code: 'AccessTokenExpired'
});
}
if (err || !user) {
throw new UnauthorizedException({
statusCode: 401,
message: 'Invalid Token',
code: 'InvalidToken'
});
}
return user;
}
}

View File

@@ -1,5 +1,45 @@
import { Injectable } from "@nestjs/common"; import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { TokenExpiredError } from "@nestjs/jwt";
import { AuthGuard } from "@nestjs/passport"; import { AuthGuard } from "@nestjs/passport";
import { IS_PUBLIC_KEY } from "src/common/decorators/public.decorator";
@Injectable() @Injectable()
export class JwtRefreshAuthGuard extends AuthGuard('refresh-token') {} export class JwtRefreshAuthGuard extends AuthGuard('refresh-token') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass()
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
handleRequest(err: any, user:any, info:any) {
if (info instanceof TokenExpiredError) {
throw new UnauthorizedException({
statusCode: 401,
message: 'Refresh Token Expired',
code: 'RefreshTokenExpired'
});
}
if (err || !user) {
throw new UnauthorizedException({
statusCode: 401,
message: 'Invalid Token',
code: 'InvalidToken'
});
}
return user;
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from "@nestjs/common"; import { Injectable, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport"; import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt"; import { ExtractJwt, Strategy } from "passport-jwt";
@@ -13,6 +13,10 @@ export class JwtAccessStrategy extends PassportStrategy(Strategy, "access-token"
} }
async validate(payload: any) { async validate(payload: any) {
const token = ExtractJwt.fromAuthHeaderAsBearerToken();
if (!token) {
throw new UnauthorizedException();
}
return { id: payload.id }; return { id: payload.id };
} }
} }

View File

@@ -13,10 +13,10 @@ export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'refresh-toke
}); });
} }
async validate(req: any, payload: any) { async validate(payload: any) {
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); const token = ExtractJwt.fromAuthHeaderAsBearerToken();
if (!token) throw new UnauthorizedException(); if (!token) throw new UnauthorizedException('Invalid Refresh Token');
return { return {
id: payload.id, id: payload.id,

View File

@@ -3,13 +3,13 @@ import { AccountService } from "./account.service";
import * as DTO from "./dto"; import * as DTO from "./dto";
import { JwtAccessAuthGuard } from "src/middleware/auth/guard/access-token.guard"; import { JwtAccessAuthGuard } from "src/middleware/auth/guard/access-token.guard";
import { Public } from "src/common/decorators/public.decorator"; import { Public } from "src/common/decorators/public.decorator";
import { JwtRefreshAuthGuard } from "src/middleware/auth/guard/refresh-token.guard";
@UseGuards(JwtAccessAuthGuard) @UseGuards(JwtAccessAuthGuard)
@Controller('account') @Controller('account')
export class AccountController { export class AccountController {
constructor(private readonly accountService: AccountService) {} constructor(private readonly accountService: AccountService) {}
@Public()
@Get('/') @Get('/')
async test() { async test() {
return "Test" return "Test"
@@ -51,7 +51,8 @@ export class AccountController {
} }
@Public() @Public()
@Post('refresh-access-token') @UseGuards(JwtRefreshAuthGuard)
@Get('refresh-access-token')
async refreshAccessToken(@Req() req): Promise<DTO.RefreshAccessTokenResponse> { async refreshAccessToken(@Req() req): Promise<DTO.RefreshAccessTokenResponse> {
const id = req.user.id; const id = req.user.id;
const newAccessToken = this.accountService.refreshAccessToken(id); const newAccessToken = this.accountService.refreshAccessToken(id);