-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 액세스 토큰 재발급 로직 AOP로 일괄 적용 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.gcp.domain.gcp.aop; | ||
|
|
||
|
|
||
| import java.lang.annotation.*; | ||
|
|
||
| @Target({ElementType.METHOD, ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Documented | ||
| public @interface RequiredValidToken { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.gcp.domain.gcp.aop; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.gcp.domain.discord.entity.DiscordUser; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.gcp.domain.discord.repository.DiscordUserRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.gcp.domain.discord.service.DiscordUserService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.aspectj.lang.annotation.Around; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.aspectj.lang.annotation.Aspect; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Aspect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class TokenAspect { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final DiscordUserRepository discordUserRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final DiscordUserService discordUserService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Around("@within(com.gcp.domain.gcp.aop.RequiredValidToken) && args(userId, guildId, ..)") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Object validateAndRefreshToken(ProceedingJoinPoint joinPoint, String userId, String guildId) throws Throwable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime tokenExp = discordUserRepository.findAccessTokenExpByUserIdAndGuildId(userId, guildId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (tokenExp.isBefore(LocalDateTime.now())) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DiscordUser discordUser = discordUserRepository.findByUserIdAndGuildId(userId, guildId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Object> reissued = discordUserService.refreshAccessToken(discordUser.getGoogleRefreshToken()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| discordUser.updateAccessToken((String) reissued.get("access_token")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| discordUser.updateAccessTokenExpiration( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.now().plusSeconds((Integer) reissued.get("expires_in")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰 업데이트 영속화 누락 및 expires_in 캐스팅 위험
아래와 같이 보완을 권장합니다. @@
- if (tokenExp.isBefore(LocalDateTime.now())) {
+ if (tokenExp.isBefore(LocalDateTime.now())) {
DiscordUser discordUser = discordUserRepository.findByUserIdAndGuildId(userId, guildId)
.orElseThrow();
- Map<String, Object> reissued = discordUserService.refreshAccessToken(discordUser.getGoogleRefreshToken());
+ String refreshToken = discordUser.getGoogleRefreshToken();
+ if (refreshToken == null || refreshToken.isBlank()) {
+ throw new IllegalStateException(
+ String.format("리프레시 토큰이 없습니다: userId=%s, guildId=%s", userId, guildId)
+ );
+ }
+ Map<String, Object> reissued = discordUserService.refreshAccessToken(refreshToken);
- discordUser.updateAccessToken((String) reissued.get("access_token"));
- discordUser.updateAccessTokenExpiration(
- LocalDateTime.now().plusSeconds((Integer) reissued.get("expires_in"))
- );
+ discordUser.updateAccessToken((String) reissued.get("access_token"));
+ Number expiresInNum = (Number) reissued.get("expires_in");
+ long expiresInSec = (expiresInNum != null) ? expiresInNum.longValue() : 3600L;
+ discordUser.updateAccessTokenExpiration(
+ // OAuth2AuthenticationSuccessHandler와 동일하게 Asia/Seoul 기준으로 저장
+ LocalDateTime.now(java.time.ZoneId.of("Asia/Seoul")).plusSeconds(expiresInSec)
+ );
+ // 트랜잭션 경계/어드바이스 순서와 무관하게 영속화 보장
+ discordUserRepository.save(discordUser);
}추가 메모:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return joinPoint.proceed(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보안: 리프레시 토큰 로그 출력 금지 권고
해당 어드바이스는 만료 시 매번 refreshAccessToken(...)을 호출합니다. 현재 DiscordUserService.refreshAccessToken 내부에서 refreshToken을 로그(INFO)로 찍고 있어 민감정보 유출 위험이 큽니다. 즉시 제거를 권장합니다.
수정 제안 (다른 파일, 참고용):
🤖 Prompt for AI Agents