From b784c2b94dbd7dc1a90f1f07227fd4a5731b4140 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Mon, 4 Nov 2024 20:54:57 +0900 Subject: [PATCH 1/9] add :: RedisProperties --- .../java/com/gcms/v3/global/redis/RedisProperties.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/redis/RedisProperties.java diff --git a/src/main/java/com/gcms/v3/global/redis/RedisProperties.java b/src/main/java/com/gcms/v3/global/redis/RedisProperties.java new file mode 100644 index 0000000..5f1d894 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/redis/RedisProperties.java @@ -0,0 +1,10 @@ +package com.gcms.v3.global.redis; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.data.redis") +public record RedisProperties ( + String host, + int port +){ +} From 3d84b50df4e49d951f7eb8872d86fbf7f04a256e Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Mon, 4 Nov 2024 20:55:08 +0900 Subject: [PATCH 2/9] add :: RedisConfig --- .../com/gcms/v3/global/redis/RedisConfig.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/redis/RedisConfig.java diff --git a/src/main/java/com/gcms/v3/global/redis/RedisConfig.java b/src/main/java/com/gcms/v3/global/redis/RedisConfig.java new file mode 100644 index 0000000..d08b8b2 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/redis/RedisConfig.java @@ -0,0 +1,29 @@ +package com.gcms.v3.global.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@RequiredArgsConstructor +public class RedisConfig { + private final RedisProperties redisProperties; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisProperties.host(), redisProperties.port()); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setValueSerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new StringRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory()); + return template; + } +} From f38d08897d4ecd02575ccd54399697da6db3002d Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Mon, 4 Nov 2024 20:55:15 +0900 Subject: [PATCH 3/9] add :: RedisUtil --- .../com/gcms/v3/global/redis/RedisUtil.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/redis/RedisUtil.java diff --git a/src/main/java/com/gcms/v3/global/redis/RedisUtil.java b/src/main/java/com/gcms/v3/global/redis/RedisUtil.java new file mode 100644 index 0000000..68ef5e5 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/redis/RedisUtil.java @@ -0,0 +1,36 @@ +package com.gcms.v3.global.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +@RequiredArgsConstructor +public class RedisUtil { + + private final RedisTemplate redisBlackListTemplate; + + public boolean checkExistsValue(String value) { + return !value.equals("false"); + } + + public void setBlackList(String key, Object o, Long milliSeconds) { + redisBlackListTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass())); + redisBlackListTemplate.opsForValue().set(key, o, milliSeconds, TimeUnit.MILLISECONDS); + } + + public Object getBlackList(String key) { + return redisBlackListTemplate.opsForValue().get(key); + } + + public boolean deleteBlackList(String key) { + return redisBlackListTemplate.delete(key); + } + + public boolean hasKeyBlackList(String key) { + return redisBlackListTemplate.hasKey(key); + } +} From feba8b93f447a0ed586ab79db0ee7d9ceece762e Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Mon, 4 Nov 2024 20:55:24 +0900 Subject: [PATCH 4/9] add :: UserUtil --- .../gcms/v3/domain/user/util/UserUtil.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/gcms/v3/domain/user/util/UserUtil.java diff --git a/src/main/java/com/gcms/v3/domain/user/util/UserUtil.java b/src/main/java/com/gcms/v3/domain/user/util/UserUtil.java new file mode 100644 index 0000000..23c95aa --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/user/util/UserUtil.java @@ -0,0 +1,21 @@ +package com.gcms.v3.domain.user.util; + +import com.gcms.v3.domain.auth.exception.UserNotFoundException; +import com.gcms.v3.domain.user.domain.entity.User; +import com.gcms.v3.domain.user.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class UserUtil { + + private final UserRepository userRepository; + + public User getCurrentUser() { + String email = SecurityContextHolder.getContext().getAuthentication().getName(); + return userRepository.findByEmail(email) + .orElseThrow(UserNotFoundException::new); + } +} From ebd5e20306827f078b9e3f2726aea7ec01c80b89 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Mon, 4 Nov 2024 20:55:46 +0900 Subject: [PATCH 5/9] add :: LogoutService --- .../repository/RefreshTokenRepository.java | 2 +- .../v3/domain/auth/service/LogoutService.java | 7 ++++ .../auth/service/impl/LogoutServiceImpl.java | 36 +++++++++++++++++++ .../global/security/jwt/JwtTokenProvider.java | 17 +++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/gcms/v3/domain/auth/service/LogoutService.java create mode 100644 src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java diff --git a/src/main/java/com/gcms/v3/domain/auth/domain/repository/RefreshTokenRepository.java b/src/main/java/com/gcms/v3/domain/auth/domain/repository/RefreshTokenRepository.java index 08172e1..73c77b2 100644 --- a/src/main/java/com/gcms/v3/domain/auth/domain/repository/RefreshTokenRepository.java +++ b/src/main/java/com/gcms/v3/domain/auth/domain/repository/RefreshTokenRepository.java @@ -8,5 +8,5 @@ public interface RefreshTokenRepository extends CrudRepository { Optional findByToken(String token); - RefreshToken findByEmail(String email); + Optional findByEmail(String email); } diff --git a/src/main/java/com/gcms/v3/domain/auth/service/LogoutService.java b/src/main/java/com/gcms/v3/domain/auth/service/LogoutService.java new file mode 100644 index 0000000..e3246a4 --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/service/LogoutService.java @@ -0,0 +1,7 @@ +package com.gcms.v3.domain.auth.service; + +import jakarta.servlet.http.HttpServletRequest; + +public interface LogoutService { + void execute(HttpServletRequest request); +} diff --git a/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java b/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java new file mode 100644 index 0000000..4e752bb --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java @@ -0,0 +1,36 @@ +package com.gcms.v3.domain.auth.service.impl; + +import com.gcms.v3.domain.auth.domain.entity.RefreshToken; +import com.gcms.v3.domain.auth.domain.repository.RefreshTokenRepository; +import com.gcms.v3.domain.auth.exception.UserNotFoundException; +import com.gcms.v3.domain.auth.service.LogoutService; +import com.gcms.v3.domain.user.domain.entity.User; +import com.gcms.v3.domain.user.util.UserUtil; +import com.gcms.v3.global.redis.RedisUtil; +import com.gcms.v3.global.security.jwt.JwtTokenProvider; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class LogoutServiceImpl implements LogoutService { + + private final UserUtil userUtil; + private final RefreshTokenRepository refreshTokenRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RedisUtil redisUtil; + + public void execute(HttpServletRequest request) { + String accessToken = jwtTokenProvider.resolveToken(request); + + User user = userUtil.getCurrentUser(); + + RefreshToken refreshToken = refreshTokenRepository.findByEmail(user.getEmail()) + .orElseThrow(UserNotFoundException::new); + + refreshTokenRepository.delete(refreshToken); + + redisUtil.setBlackList(accessToken, "access_token", jwtTokenProvider.getExpiration(accessToken)); + } +} diff --git a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java index 302bd08..732c684 100644 --- a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java @@ -1,6 +1,7 @@ package com.gcms.v3.global.security.jwt; import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; +import com.gcms.v3.global.redis.RedisUtil; import com.gcms.v3.global.security.exception.InvalidAuthTokenException; import com.gcms.v3.global.security.auth.AuthDetailsService; import io.jsonwebtoken.*; @@ -37,6 +38,7 @@ public class JwtTokenProvider { private static Key refreshtokenkey; private final AuthDetailsService authDetailsService; private final JwtProperties jwtProperties; + private final RedisUtil redisUtil; @PostConstruct public void init() { @@ -116,6 +118,11 @@ public String resolveToken(HttpServletRequest request) { public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(accessTokenkey).build().parseClaimsJws(token); + + if (redisUtil.hasKeyBlackList(token)) { + throw new InvalidAuthTokenException(); + } + return true; } catch (SecurityException | MalformedJwtException e) { throw new InvalidAuthTokenException(); @@ -148,4 +155,14 @@ private Claims getTokenBody(String token, Key secret) { .parseClaimsJws(token) .getBody(); } + + public Long getExpiration(String accessToken) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(accessTokenkey) + .build() + .parseClaimsJws(accessToken) + .getBody(); + + return claims.getExpiration().getTime(); + } } From 025a63ca4d09e06d57a64bc4e3670c09036304a5 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Mon, 4 Nov 2024 20:55:55 +0900 Subject: [PATCH 6/9] =?UTF-8?q?add=20::=20endpoint=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gcms/v3/domain/auth/presentation/AuthController.java | 9 +++++++++ .../gcms/v3/global/security/config/SecurityConfig.java | 1 + 2 files changed, 10 insertions(+) diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java b/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java index 0e78609..1bcd88d 100644 --- a/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java @@ -2,8 +2,10 @@ import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; +import com.gcms.v3.domain.auth.service.LogoutService; import com.gcms.v3.domain.auth.service.ReissueTokenService; import com.gcms.v3.domain.auth.service.SignInService; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -15,6 +17,7 @@ public class AuthController { private final SignInService signInService; private final ReissueTokenService reissueTokenService; + private final LogoutService logoutService; @PostMapping public ResponseEntity signIn (@RequestBody SignInRequestDto signInRequestDto) { @@ -27,4 +30,10 @@ public ResponseEntity reissueToken (@RequestBody String re TokenInfoResponseDto res = reissueTokenService.execute(refreshToken); return ResponseEntity.ok(res); } + + @DeleteMapping("/logout") + public ResponseEntity logout(HttpServletRequest request) { + logoutService.execute(request); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java b/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java index 64d13fe..21ef9a4 100644 --- a/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java +++ b/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java @@ -44,6 +44,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti authorizeRequests .requestMatchers(HttpMethod.POST, "/v3/auth").permitAll() .requestMatchers(HttpMethod.POST, "/v3/auth/reissueToken").authenticated() + .requestMatchers(HttpMethod.DELETE, "/v3/auth/logout").authenticated() ) .addFilterBefore(new ExceptionFilter(objectMapper), UsernamePasswordAuthenticationFilter.class) From 4350cd38194107dc45621a9772ad591694413095 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Wed, 6 Nov 2024 11:18:50 +0900 Subject: [PATCH 7/9] refactor :: @Transactional --- src/main/java/com/gcms/v3/global/redis/RedisConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/gcms/v3/global/redis/RedisConfig.java b/src/main/java/com/gcms/v3/global/redis/RedisConfig.java index d08b8b2..0e347f7 100644 --- a/src/main/java/com/gcms/v3/global/redis/RedisConfig.java +++ b/src/main/java/com/gcms/v3/global/redis/RedisConfig.java @@ -1,5 +1,6 @@ package com.gcms.v3.global.redis; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,11 +20,13 @@ public RedisConnectionFactory redisConnectionFactory() { } @Bean + @Transactional public RedisTemplate redisTemplate() { RedisTemplate template = new RedisTemplate<>(); template.setValueSerializer(new StringRedisSerializer()); template.setHashValueSerializer(new StringRedisSerializer()); template.setConnectionFactory(redisConnectionFactory()); + template.setEnableTransactionSupport(true); return template; } } From 46a9d0c9ab8143ca7fdd02b055bd68b5131746e2 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Wed, 6 Nov 2024 11:19:11 +0900 Subject: [PATCH 8/9] =?UTF-8?q?delete=20::=20deleteBlackList=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/gcms/v3/global/redis/RedisUtil.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/gcms/v3/global/redis/RedisUtil.java b/src/main/java/com/gcms/v3/global/redis/RedisUtil.java index 68ef5e5..b615c06 100644 --- a/src/main/java/com/gcms/v3/global/redis/RedisUtil.java +++ b/src/main/java/com/gcms/v3/global/redis/RedisUtil.java @@ -26,10 +26,6 @@ public Object getBlackList(String key) { return redisBlackListTemplate.opsForValue().get(key); } - public boolean deleteBlackList(String key) { - return redisBlackListTemplate.delete(key); - } - public boolean hasKeyBlackList(String key) { return redisBlackListTemplate.hasKey(key); } From 916008bf56b999cf67c6a9663f8527b708c6d08e Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 8 Nov 2024 07:41:46 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor=20::=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java | 2 ++ src/main/java/com/gcms/v3/global/redis/RedisConfig.java | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java b/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java index 4e752bb..808549e 100644 --- a/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java +++ b/src/main/java/com/gcms/v3/domain/auth/service/impl/LogoutServiceImpl.java @@ -9,11 +9,13 @@ import com.gcms.v3.global.redis.RedisUtil; import com.gcms.v3.global.security.jwt.JwtTokenProvider; import jakarta.servlet.http.HttpServletRequest; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service +@Transactional public class LogoutServiceImpl implements LogoutService { private final UserUtil userUtil; diff --git a/src/main/java/com/gcms/v3/global/redis/RedisConfig.java b/src/main/java/com/gcms/v3/global/redis/RedisConfig.java index 0e347f7..ddbfe37 100644 --- a/src/main/java/com/gcms/v3/global/redis/RedisConfig.java +++ b/src/main/java/com/gcms/v3/global/redis/RedisConfig.java @@ -1,6 +1,5 @@ package com.gcms.v3.global.redis; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,7 +19,6 @@ public RedisConnectionFactory redisConnectionFactory() { } @Bean - @Transactional public RedisTemplate redisTemplate() { RedisTemplate template = new RedisTemplate<>(); template.setValueSerializer(new StringRedisSerializer());