diff --git a/src/main/java/clap/server/adapter/inbound/security/filter/JwtAuthenticationFilter.java b/src/main/java/clap/server/adapter/inbound/security/filter/JwtAuthenticationFilter.java index 7cf491b7..17b91566 100644 --- a/src/main/java/clap/server/adapter/inbound/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/clap/server/adapter/inbound/security/filter/JwtAuthenticationFilter.java @@ -2,6 +2,7 @@ import clap.server.adapter.outbound.jwt.JwtClaims; import clap.server.adapter.outbound.jwt.access.AccessTokenClaimKeys; +import clap.server.application.port.outbound.auth.ForbiddenTokenPort; import clap.server.application.port.outbound.auth.JwtProvider; import clap.server.exception.JwtException; import clap.server.exception.code.AuthErrorCode; @@ -27,7 +28,6 @@ import java.io.IOException; -// 요청에서 JWT 토큰을 추출하고 유효성을 검사합니다. @Slf4j @Component @RequiredArgsConstructor @@ -37,6 +37,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtProvider accessTokenProvider; private final JwtProvider temporaryTokenProvider; private final AccessDeniedHandler accessDeniedHandler; + private final ForbiddenTokenPort forbiddenTokenPort; @Override protected void doFilterInternal( @@ -70,15 +71,15 @@ private String resolveAccessToken( HttpServletRequest request ) throws ServletException { String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); - String token = accessTokenProvider.resolveToken(authHeader); + String accessToken = accessTokenProvider.resolveToken(authHeader); - if (!StringUtils.hasText(token)) { + if (!StringUtils.hasText(accessToken)) { log.error("EMPTY_ACCESS_TOKEN"); handleAuthException(AuthErrorCode.EMPTY_ACCESS_KEY); } String requestUrl = request.getRequestURI(); - boolean isTemporaryToken = isTemporaryToken(token); + boolean isTemporaryToken = isTemporaryToken(accessToken); JwtProvider tokenProvider = isTemporaryToken ? temporaryTokenProvider : accessTokenProvider; log.info("Token is Temporary {}", isTemporaryToken); @@ -88,14 +89,17 @@ private String resolveAccessToken( handleAuthException(AuthErrorCode.FORBIDDEN_ACCESS_TOKEN); } - // TODO: 블랙리스트 토큰 처리 로직 추가 필요 + if (forbiddenTokenPort.getIsForbidden(accessToken)) { + log.error("FORBIDDEN_ACCESS_TOKEN"); + handleAuthException(AuthErrorCode.FORBIDDEN_ACCESS_TOKEN); + } - if (tokenProvider.isTokenExpired(token)) { + if (tokenProvider.isTokenExpired(accessToken)) { log.error("EXPIRED_TOKEN"); handleAuthException(AuthErrorCode.EXPIRED_TOKEN); } - return token; + return accessToken; } diff --git a/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java b/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java index 3c1eb17b..7e189ceb 100644 --- a/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java +++ b/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java @@ -1,19 +1,19 @@ package clap.server.adapter.inbound.web.auth; +import clap.server.adapter.inbound.security.SecurityUserDetails; import clap.server.adapter.inbound.web.dto.auth.LoginRequest; import clap.server.adapter.inbound.web.dto.auth.LoginResponse; import clap.server.application.port.inbound.auth.AuthUsecase; import clap.server.common.annotation.architecture.WebAdapter; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; import static clap.server.common.utils.ClientIpParseUtil.getClientIp; @@ -35,5 +35,13 @@ public ResponseEntity login(@RequestHeader(name = "sessionId") St return ResponseEntity.ok(response); } + @Operation(summary = "로그아웃 API") + @DeleteMapping("/logout") + public void logout(@AuthenticationPrincipal SecurityUserDetails userInfo, + @Parameter(hidden = true) @RequestHeader(value = "Authorization") String authHeader, + @RequestHeader(value = "refreshToken") String refreshToken) { + String accessToken = authHeader.split(" ")[1]; + authUsecase.logout(userInfo.getUserId(), accessToken, refreshToken); + } } diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenAdapter.java new file mode 100644 index 00000000..d2831229 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenAdapter.java @@ -0,0 +1,23 @@ +package clap.server.adapter.outbound.infrastructure.redis.forbidden; + +import clap.server.application.port.outbound.auth.ForbiddenTokenPort; +import clap.server.common.annotation.architecture.InfrastructureAdapter; +import clap.server.domain.model.auth.ForbiddenToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@InfrastructureAdapter +@RequiredArgsConstructor +public class ForbiddenTokenAdapter implements ForbiddenTokenPort { + private final ForbiddenTokenRepository forbiddenTokenRepository; + private final ForbiddenTokenMapper forbiddenTokenMapper; + + public void save(ForbiddenToken forbiddenToken) { + forbiddenTokenRepository.save(forbiddenTokenMapper.toEntity(forbiddenToken)); + } + + public boolean getIsForbidden(String accessToken) { + return forbiddenTokenRepository.existsById(accessToken); + } +} diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenEntity.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenEntity.java new file mode 100644 index 00000000..190dcb49 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenEntity.java @@ -0,0 +1,43 @@ +package clap.server.adapter.outbound.infrastructure.redis.forbidden; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +@Getter +@RedisHash("forbiddenToken") +public class ForbiddenTokenEntity { + @Id + private final String accessToken; + private final Long userId; + + @TimeToLive + private final long ttl; + + @Builder + private ForbiddenTokenEntity(String accessToken, Long userId, long ttl) { + this.accessToken = accessToken; + this.userId = userId; + this.ttl = ttl; + } + + public static ForbiddenTokenEntity of(String accessToken, Long userId, long ttl) { + return new ForbiddenTokenEntity(accessToken, userId, ttl); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ForbiddenTokenEntity that)) return false; + return accessToken.equals(that.accessToken) && userId.equals(that.userId); + } + + @Override + public int hashCode() { + int result = accessToken.hashCode(); + result = ((1 << 5) - 1) * result + userId.hashCode(); + return result; + } +} diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenMapper.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenMapper.java new file mode 100644 index 00000000..97775416 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenMapper.java @@ -0,0 +1,13 @@ +package clap.server.adapter.outbound.infrastructure.redis.forbidden; + +import clap.server.domain.model.auth.ForbiddenToken; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface ForbiddenTokenMapper { + @InheritInverseConfiguration + ForbiddenToken toDomain(final ForbiddenTokenEntity entity); + + ForbiddenTokenEntity toEntity(final ForbiddenToken domain); +} \ No newline at end of file diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenRepository.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenRepository.java new file mode 100644 index 00000000..642a5a94 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/forbidden/ForbiddenTokenRepository.java @@ -0,0 +1,6 @@ +package clap.server.adapter.outbound.infrastructure.redis.forbidden; + +import org.springframework.data.repository.CrudRepository; + +public interface ForbiddenTokenRepository extends CrudRepository { +} \ No newline at end of file diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java index 5770818d..b2643307 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java @@ -2,15 +2,15 @@ import clap.server.application.port.outbound.auth.CommandLoginLogPort; import clap.server.application.port.outbound.auth.LoadLoginLogPort; +import clap.server.common.annotation.architecture.InfrastructureAdapter; import clap.server.domain.model.auth.LoginLog; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; import java.util.Optional; @Slf4j -@Component +@InfrastructureAdapter @RequiredArgsConstructor public class LoginLogAdapter implements LoadLoginLogPort, CommandLoginLogPort { private final LoginLogRepository loginLogRepository; diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/refresh/RefreshTokenAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/refresh/RefreshTokenAdapter.java index 94d4f4d0..7c91371f 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/refresh/RefreshTokenAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/refresh/RefreshTokenAdapter.java @@ -2,15 +2,15 @@ import clap.server.application.port.outbound.auth.CommandRefreshTokenPort; import clap.server.application.port.outbound.auth.LoadRefreshTokenPort; +import clap.server.common.annotation.architecture.InfrastructureAdapter; import clap.server.domain.model.auth.RefreshToken; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; import java.util.Optional; @Slf4j -@Component +@InfrastructureAdapter @RequiredArgsConstructor public class RefreshTokenAdapter implements CommandRefreshTokenPort, LoadRefreshTokenPort { private final RefreshTokenRepository refreshTokenRepository; diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java index 450dba10..44c7149e 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java @@ -1,13 +1,13 @@ package clap.server.adapter.outbound.infrastructure.s3; import clap.server.application.port.outbound.s3.S3UploadPort; +import clap.server.common.annotation.architecture.InfrastructureAdapter; import clap.server.config.s3.KakaoS3Config; import clap.server.domain.model.task.FilePath; import clap.server.exception.S3Exception; import clap.server.exception.code.S3Errorcode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @@ -20,7 +20,7 @@ import java.util.UUID; @Slf4j -@Service +@InfrastructureAdapter @RequiredArgsConstructor public class S3UploadAdapter implements S3UploadPort { private final KakaoS3Config kakaoS3Config; diff --git a/src/main/java/clap/server/application/port/inbound/auth/AuthUsecase.java b/src/main/java/clap/server/application/port/inbound/auth/AuthUsecase.java index e67e91c7..85307841 100644 --- a/src/main/java/clap/server/application/port/inbound/auth/AuthUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/auth/AuthUsecase.java @@ -4,4 +4,5 @@ public interface AuthUsecase { LoginResponse login(String nickname, String password, String sessionId, String clientIp); + void logout(Long memberId, String accessToken, String refreshToken); } diff --git a/src/main/java/clap/server/application/port/outbound/auth/ForbiddenTokenPort.java b/src/main/java/clap/server/application/port/outbound/auth/ForbiddenTokenPort.java new file mode 100644 index 00000000..1e73bf83 --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/auth/ForbiddenTokenPort.java @@ -0,0 +1,8 @@ +package clap.server.application.port.outbound.auth; + +import clap.server.domain.model.auth.ForbiddenToken; + +public interface ForbiddenTokenPort { + void save(ForbiddenToken forbiddenToken); + boolean getIsForbidden(String accessToken); +} diff --git a/src/main/java/clap/server/application/service/auth/AuthService.java b/src/main/java/clap/server/application/service/auth/AuthService.java index 0162d8cd..a87539e0 100644 --- a/src/main/java/clap/server/application/service/auth/AuthService.java +++ b/src/main/java/clap/server/application/service/auth/AuthService.java @@ -4,10 +4,12 @@ import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus; import clap.server.application.mapper.response.AuthResponseMapper; import clap.server.application.port.inbound.auth.AuthUsecase; -import clap.server.application.port.outbound.auth.CommandRefreshTokenPort; +import clap.server.application.port.outbound.auth.ForbiddenTokenPort; import clap.server.application.port.outbound.member.LoadMemberPort; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.auth.CustomJwts; +import clap.server.domain.model.auth.ForbiddenToken; +import clap.server.domain.model.auth.RefreshToken; import clap.server.domain.model.member.Member; import clap.server.exception.AuthException; import clap.server.exception.code.AuthErrorCode; @@ -16,34 +18,57 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.time.LocalDateTime; + @Slf4j @ApplicationService @RequiredArgsConstructor +@Transactional class AuthService implements AuthUsecase { private final LoadMemberPort loadMemberPort; - private final CommandRefreshTokenPort commandRefreshTokenPort; - private final IssueTokenService issueTokenService; + private final ManageTokenService manageTokenService; private final PasswordEncoder passwordEncoder; private final LoginAttemptService loginAttemptService; + private final RefreshTokenService refreshTokenService; + private final ForbiddenTokenPort forbiddenTokenPort; + @Override - @Transactional public LoginResponse login(String nickname, String password, String sessionId, String clientIp) { Member member = getMember(nickname, sessionId, clientIp); validatePassword(password, member.getPassword(), sessionId, nickname, clientIp); if (member.getStatus().equals(MemberStatus.APPROVAL_REQUEST)) { - String temporaryToken = issueTokenService.issueTemporaryToken(member.getMemberId()); + String temporaryToken = manageTokenService.issueTemporaryToken(member.getMemberId()); return AuthResponseMapper.toLoginResponse(temporaryToken, null, member); } - CustomJwts jwtTokens = issueTokenService.issueTokens(member); - commandRefreshTokenPort.save(issueTokenService.issueRefreshToken(member.getMemberId())); + CustomJwts jwtTokens = manageTokenService.issueTokens(member); + refreshTokenService.saveRefreshToken(manageTokenService.issueRefreshToken(member.getMemberId())); loginAttemptService.resetFailedAttempts(sessionId); return AuthResponseMapper.toLoginResponse(jwtTokens.accessToken(), jwtTokens.refreshToken(), member); } + @Override + public void logout(Long memberId, String accessToken, String refreshToken) { + RefreshToken refreshTokenFindByMember = refreshTokenService.getRefreshToken(memberId); + refreshTokenService.validateToken(refreshToken, refreshTokenFindByMember); + refreshTokenService.deleteRefreshToken(refreshTokenFindByMember); + deleteAccessToken(memberId, accessToken); + } + + private void deleteAccessToken(Long memberId, String accessToken) { + LocalDateTime expiredDate = manageTokenService.getExpiredDate(accessToken); + + LocalDateTime now = LocalDateTime.now(); + long timeToLive = Duration.between(now, expiredDate).toSeconds(); + + ForbiddenToken forbiddenToken = ForbiddenToken.of(accessToken, memberId, timeToLive); + forbiddenTokenPort.save(forbiddenToken); + } + private Member getMember(String inputNickname, String sessionId, String clientIp) { return loadMemberPort.findByNickname(inputNickname).orElseThrow(() -> { diff --git a/src/main/java/clap/server/application/service/auth/IssueTokenService.java b/src/main/java/clap/server/application/service/auth/ManageTokenService.java similarity index 94% rename from src/main/java/clap/server/application/service/auth/IssueTokenService.java rename to src/main/java/clap/server/application/service/auth/ManageTokenService.java index 2b8770dd..fecfdc5a 100644 --- a/src/main/java/clap/server/application/service/auth/IssueTokenService.java +++ b/src/main/java/clap/server/application/service/auth/ManageTokenService.java @@ -20,7 +20,7 @@ @RequiredArgsConstructor @Component @Slf4j -class IssueTokenService { +class ManageTokenService { private final JwtProvider accessTokenProvider; private final JwtProvider refreshTokenProvider; private final JwtProvider temporaryTokenProvider; @@ -35,7 +35,6 @@ public String issueAccessToken(Long memberId) { return accessTokenProvider.createToken(AccessTokenClaim.of(memberId)); } - public RefreshToken issueRefreshToken(Long memberId) { String refreshToken = refreshTokenProvider.createToken(RefreshTokenClaim.of(memberId)); return RefreshToken.of( @@ -57,6 +56,10 @@ public Long resolveRefreshToken(String refreshToken) { Long::parseLong); } + public LocalDateTime getExpiredDate(String accessToken) { + return accessTokenProvider.getExpiredDate(accessToken); + } + private long toSeconds(LocalDateTime expiredDate) { return Duration.between(LocalDateTime.now(), expiredDate).getSeconds(); } diff --git a/src/main/java/clap/server/application/service/auth/RefreshTokenService.java b/src/main/java/clap/server/application/service/auth/RefreshTokenService.java new file mode 100644 index 00000000..ecec64a3 --- /dev/null +++ b/src/main/java/clap/server/application/service/auth/RefreshTokenService.java @@ -0,0 +1,43 @@ +package clap.server.application.service.auth; + +import clap.server.application.port.outbound.auth.CommandRefreshTokenPort; +import clap.server.application.port.outbound.auth.LoadRefreshTokenPort; +import clap.server.domain.model.auth.RefreshToken; +import clap.server.exception.AuthException; +import clap.server.exception.code.AuthErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static clap.server.domain.model.auth.RefreshToken.isTakenAway; + +@RequiredArgsConstructor +@Component +@Slf4j +public class RefreshTokenService { + private final LoadRefreshTokenPort loadRefreshTokenPort; + private final CommandRefreshTokenPort commandRefreshTokenPort; + + public RefreshToken getRefreshToken(Long memberId) { + return loadRefreshTokenPort.findByMemberId(memberId).orElseThrow( + () -> new AuthException(AuthErrorCode.REFRESH_TOKEN_NOT_FOUND) + ); + } + + public void saveRefreshToken(RefreshToken token) { + commandRefreshTokenPort.save(token); + } + + public void deleteRefreshToken(RefreshToken token) { + commandRefreshTokenPort.delete(token); + } + + public void validateToken(String oldRefreshToken, RefreshToken refreshToken) { + if (isTakenAway(oldRefreshToken, refreshToken.getToken())) { + commandRefreshTokenPort.delete(refreshToken); + throw new AuthException(AuthErrorCode.REFRESH_TOKEN_MISMATCHED); + } + } + + +} diff --git a/src/main/java/clap/server/application/service/auth/ReissueTokenService.java b/src/main/java/clap/server/application/service/auth/ReissueTokenService.java index 022dd9e2..443a94d3 100644 --- a/src/main/java/clap/server/application/service/auth/ReissueTokenService.java +++ b/src/main/java/clap/server/application/service/auth/ReissueTokenService.java @@ -3,7 +3,6 @@ import clap.server.adapter.inbound.web.dto.auth.ReissueTokenResponse; import clap.server.application.port.inbound.auth.ReissueTokenUsecase; import clap.server.application.port.outbound.auth.CommandRefreshTokenPort; -import clap.server.application.port.outbound.auth.LoadRefreshTokenPort; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.auth.CustomJwts; import clap.server.domain.model.auth.RefreshToken; @@ -17,24 +16,24 @@ @ApplicationService @RequiredArgsConstructor class ReissueTokenService implements ReissueTokenUsecase { - private final IssueTokenService issueTokenService; - private final LoadRefreshTokenPort loadRefreshTokenPort; + private final ManageTokenService manageTokenService; private final CommandRefreshTokenPort commandRefreshTokenPort; + private final RefreshTokenService refreshTokenService; @Transactional public ReissueTokenResponse reissueToken(String oldRefreshToken) { - Long memberId = issueTokenService.resolveRefreshToken(oldRefreshToken); + Long memberId = manageTokenService.resolveRefreshToken(oldRefreshToken); RefreshToken newRefreshToken; try { newRefreshToken = refresh(memberId, oldRefreshToken, - issueTokenService.issueRefreshToken(memberId).getToken()); + manageTokenService.issueRefreshToken(memberId).getToken()); } catch (IllegalArgumentException e) { throw new AuthException(AuthErrorCode.EXPIRED_TOKEN); } catch (IllegalStateException e) { throw new AuthException(AuthErrorCode.TAKEN_AWAY_TOKEN); } - String newAccessToken = issueTokenService.issueAccessToken(memberId); + String newAccessToken = manageTokenService.issueAccessToken(memberId); CustomJwts tokens = CustomJwts.of(newAccessToken, newRefreshToken.getToken()); return toReissueTokenResponse(tokens); } @@ -44,25 +43,12 @@ private RefreshToken refresh( String oldRefreshToken, String newRefreshToken ) throws IllegalArgumentException, IllegalStateException { - RefreshToken refreshToken = loadRefreshTokenPort.findByMemberId(memberId).orElseThrow( - () -> new AuthException(AuthErrorCode.REFRESH_TOKEN_NOT_FOUND) - ); - validateToken(oldRefreshToken, refreshToken); + RefreshToken refreshToken = refreshTokenService.getRefreshToken(memberId); + refreshTokenService.validateToken(oldRefreshToken, refreshToken); refreshToken.rotation(newRefreshToken); commandRefreshTokenPort.save(refreshToken); return refreshToken; } - private void validateToken(String oldRefreshToken, RefreshToken refreshToken) { - if (isTakenAway(oldRefreshToken, refreshToken.getToken())) { - commandRefreshTokenPort.delete(refreshToken); - throw new AuthException(AuthErrorCode.REFRESH_TOKEN_MISMATCHED); - } - } - - private boolean isTakenAway(String requestRefreshToken, String expectedRefreshToken) { - return !requestRefreshToken.equals(expectedRefreshToken); - } - } diff --git a/src/main/java/clap/server/config/security/SecurityFilterConfig.java b/src/main/java/clap/server/config/security/SecurityFilterConfig.java index 1f454f86..89676838 100644 --- a/src/main/java/clap/server/config/security/SecurityFilterConfig.java +++ b/src/main/java/clap/server/config/security/SecurityFilterConfig.java @@ -3,6 +3,7 @@ import clap.server.adapter.inbound.security.LoginAttemptFilter; import clap.server.adapter.inbound.security.filter.JwtAuthenticationFilter; import clap.server.adapter.inbound.security.filter.JwtExceptionFilter; +import clap.server.application.port.outbound.auth.ForbiddenTokenPort; import clap.server.application.port.outbound.auth.JwtProvider; import clap.server.application.service.auth.LoginAttemptService; import lombok.AccessLevel; @@ -20,6 +21,7 @@ public class SecurityFilterConfig { private final JwtProvider accessTokenProvider; private final JwtProvider temporaryTokenProvider; private final AccessDeniedHandler accessDeniedHandler; + private final ForbiddenTokenPort forbiddenTokenPort; @Bean public JwtExceptionFilter jwtExceptionFilter() { @@ -28,7 +30,8 @@ public JwtExceptionFilter jwtExceptionFilter() { @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { - return new JwtAuthenticationFilter(securityUserDetails, accessTokenProvider, temporaryTokenProvider, accessDeniedHandler); + return new JwtAuthenticationFilter(securityUserDetails, accessTokenProvider, + temporaryTokenProvider, accessDeniedHandler, forbiddenTokenPort); } @Bean diff --git a/src/main/java/clap/server/domain/model/auth/ForbiddenToken.java b/src/main/java/clap/server/domain/model/auth/ForbiddenToken.java new file mode 100644 index 00000000..fe87e698 --- /dev/null +++ b/src/main/java/clap/server/domain/model/auth/ForbiddenToken.java @@ -0,0 +1,8 @@ +package clap.server.domain.model.auth; + +public record ForbiddenToken(String accessToken, Long userId, long ttl) { + + public static ForbiddenToken of(String accessToken, Long userId, long ttl) { + return new ForbiddenToken(accessToken, userId, ttl); + } +} \ No newline at end of file diff --git a/src/main/java/clap/server/domain/model/auth/RefreshToken.java b/src/main/java/clap/server/domain/model/auth/RefreshToken.java index 58cb6a43..fa090ab9 100644 --- a/src/main/java/clap/server/domain/model/auth/RefreshToken.java +++ b/src/main/java/clap/server/domain/model/auth/RefreshToken.java @@ -1,6 +1,9 @@ package clap.server.domain.model.auth; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Getter @@ -29,4 +32,8 @@ public static RefreshToken of(Long memberId, String token, long ttl) { public void rotation(String token) { this.token = token; } + + public static boolean isTakenAway(String requestRefreshToken, String expectedRefreshToken) { + return !requestRefreshToken.equals(expectedRefreshToken); + } }