From 0f6aa29e64a0c93f3e20ab406c06590ed04eed3c Mon Sep 17 00:00:00 2001 From: koosco Date: Sun, 29 Sep 2024 03:49:48 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix;=20redis=20container=20port=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.bootrun.yml | 4 +++- docker-compose.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-compose.bootrun.yml b/docker-compose.bootrun.yml index c5da96a..e2dc958 100644 --- a/docker-compose.bootrun.yml +++ b/docker-compose.bootrun.yml @@ -19,8 +19,10 @@ services: - "3306:3306" redis: image: redis:alpine - command: redis-server --port 6379 + command: redis-server container_name: redis.gooiman.internal + ports: + - "6379:6379" sqlpad: image: sqlpad/sqlpad ports: diff --git a/docker-compose.yml b/docker-compose.yml index c72cecc..fe270b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,9 @@ services: timeout: 10s redis: image: redis:alpine - command: redis-server --port 6379 + command: redis-server + ports: + - "6379:6379" container_name: redis.gooiman.internal api: build: From 84c8e12274196edfe9a53b660151c18fb2206b9a Mon Sep 17 00:00:00 2001 From: koosco Date: Sun, 29 Sep 2024 06:16:02 +0900 Subject: [PATCH 2/5] =?UTF-8?q?setting:=20redis=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index e19ff5a..75bff62 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.+") implementation("com.fasterxml.jackson.core:jackson-databind:2.17.+") + implementation("org.springframework.boot:spring-boot-starter-data-redis") compileOnly("org.projectlombok:lombok") developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-docker-compose") From 561af28b1f20c4dfeb4b10768b6e9001b27c7102 Mon Sep 17 00:00:00 2001 From: koosco Date: Sun, 29 Sep 2024 06:18:11 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20jwt=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EC=9D=BC=EA=B4=80?= =?UTF-8?q?=EC=84=B1=20=EC=9E=88=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JwtAuthentication -> Jwt --- ...nticationService.java => AuthService.java} | 19 +++++++++++++------ ...enticationService.java => JwtService.java} | 2 +- .../auth/controller/AuthController.java | 14 +++++++++++--- .../server/config/security/JwtConfig.java | 12 +++++++++--- .../config/security/SecurityConfig.java | 4 ++-- .../config/security/fliter/JwtFilter.java | 6 +++--- ...ticationProvider.java => JwtProvider.java} | 6 ++++-- 7 files changed, 43 insertions(+), 20 deletions(-) rename src/main/java/dev/gooiman/server/auth/application/{CustomAuthenticationService.java => AuthService.java} (88%) rename src/main/java/dev/gooiman/server/auth/application/{JwtAuthenticationService.java => JwtService.java} (97%) rename src/main/java/dev/gooiman/server/config/security/provider/{JwtAuthenticationProvider.java => JwtProvider.java} (94%) diff --git a/src/main/java/dev/gooiman/server/auth/application/CustomAuthenticationService.java b/src/main/java/dev/gooiman/server/auth/application/AuthService.java similarity index 88% rename from src/main/java/dev/gooiman/server/auth/application/CustomAuthenticationService.java rename to src/main/java/dev/gooiman/server/auth/application/AuthService.java index b65eaf6..24d2266 100644 --- a/src/main/java/dev/gooiman/server/auth/application/CustomAuthenticationService.java +++ b/src/main/java/dev/gooiman/server/auth/application/AuthService.java @@ -1,13 +1,14 @@ package dev.gooiman.server.auth.application; -import dev.gooiman.server.page.application.PageService; -import dev.gooiman.server.page.repository.entity.Page; +import dev.gooiman.server.auth.application.domain.CustomUserDetails; import dev.gooiman.server.auth.application.dto.JwtResponseDto; import dev.gooiman.server.auth.application.dto.LoginRequestDto; -import dev.gooiman.server.config.security.provider.CustomAuthenticationProvider; -import dev.gooiman.server.auth.application.domain.CustomUserDetails; import dev.gooiman.server.auth.repository.UserRepository; import dev.gooiman.server.auth.repository.entity.User; +import dev.gooiman.server.common.dto.CommonSuccessDto; +import dev.gooiman.server.config.security.provider.CustomAuthenticationProvider; +import dev.gooiman.server.page.application.PageService; +import dev.gooiman.server.page.repository.entity.Page; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -21,10 +22,11 @@ @Service @RequiredArgsConstructor -public class CustomAuthenticationService { +public class AuthService { - private final JwtAuthenticationService jwtAuthenticationService; + private final JwtService jwtAuthenticationService; private final CustomAuthenticationProvider authenticationProvider; + private final BlackListService blackListService; private final PageService pageService; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; @@ -55,4 +57,9 @@ public Authentication signup(UUID pageId, String name, String password) { return new UsernamePasswordAuthenticationToken(new CustomUserDetails(saveUser), null, List.of(new SimpleGrantedAuthority("ROLE_USER"))); } + + @Transactional + public CommonSuccessDto signout(String token) { + return blackListService.saveBlackList(token); + } } diff --git a/src/main/java/dev/gooiman/server/auth/application/JwtAuthenticationService.java b/src/main/java/dev/gooiman/server/auth/application/JwtService.java similarity index 97% rename from src/main/java/dev/gooiman/server/auth/application/JwtAuthenticationService.java rename to src/main/java/dev/gooiman/server/auth/application/JwtService.java index dbd04b8..7e6ee6b 100644 --- a/src/main/java/dev/gooiman/server/auth/application/JwtAuthenticationService.java +++ b/src/main/java/dev/gooiman/server/auth/application/JwtService.java @@ -16,7 +16,7 @@ @Service @RequiredArgsConstructor -public class JwtAuthenticationService { +public class JwtService { private static SecretKey key; diff --git a/src/main/java/dev/gooiman/server/auth/controller/AuthController.java b/src/main/java/dev/gooiman/server/auth/controller/AuthController.java index 47caa45..d830fcf 100644 --- a/src/main/java/dev/gooiman/server/auth/controller/AuthController.java +++ b/src/main/java/dev/gooiman/server/auth/controller/AuthController.java @@ -1,12 +1,14 @@ package dev.gooiman.server.auth.controller; -import dev.gooiman.server.auth.application.CustomAuthenticationService; +import dev.gooiman.server.auth.application.AuthService; import dev.gooiman.server.auth.application.dto.JwtResponseDto; import dev.gooiman.server.auth.application.dto.LoginRequestDto; +import dev.gooiman.server.common.dto.CommonSuccessDto; import dev.gooiman.server.common.dto.ResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirements; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PathVariable; @@ -21,13 +23,19 @@ @Tag(name = "Authentication", description = "인증 처리 API") public class AuthController { - private final CustomAuthenticationService authenticationService; + private final AuthService authService; @PostMapping("/login/{page_id}") @Operation(summary = "로그인", description = "로그인을 수행합니다. 만약 한번도 로그인 한 적 없는 name으로 로그인을 시도할 경우 회원가입을 수행합니다.") @SecurityRequirements public ResponseDto signIn(@PathVariable("page_id") UUID pageId, @RequestBody LoginRequestDto dto) { - return ResponseDto.ok(authenticationService.login(pageId, dto)); + return ResponseDto.ok(authService.login(pageId, dto)); + } + + @PostMapping("/logout") + public ResponseDto signout(HttpServletRequest request) { + String token = request.getHeader("Authorization"); + return ResponseDto.ok(authService.signout(token)); } } diff --git a/src/main/java/dev/gooiman/server/config/security/JwtConfig.java b/src/main/java/dev/gooiman/server/config/security/JwtConfig.java index 508f486..d2843d6 100644 --- a/src/main/java/dev/gooiman/server/config/security/JwtConfig.java +++ b/src/main/java/dev/gooiman/server/config/security/JwtConfig.java @@ -1,7 +1,9 @@ package dev.gooiman.server.config.security; import dev.gooiman.server.config.security.fliter.JwtFilter; -import dev.gooiman.server.config.security.provider.JwtAuthenticationProvider; +import dev.gooiman.server.config.security.provider.JwtProvider; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; @@ -10,9 +12,13 @@ public class JwtConfig extends SecurityConfigurerAdapter { - private final JwtAuthenticationProvider jwtAuthenticationProvider; + @Getter + @Value("${spring.security.blacklist-validity-time") + private Long blacklistValidityTime; - public JwtConfig(JwtAuthenticationProvider jwtAuthenticationProvider) { + private final JwtProvider jwtAuthenticationProvider; + + public JwtConfig(JwtProvider jwtAuthenticationProvider) { this.jwtAuthenticationProvider = jwtAuthenticationProvider; } diff --git a/src/main/java/dev/gooiman/server/config/security/SecurityConfig.java b/src/main/java/dev/gooiman/server/config/security/SecurityConfig.java index db21d97..6dab78b 100644 --- a/src/main/java/dev/gooiman/server/config/security/SecurityConfig.java +++ b/src/main/java/dev/gooiman/server/config/security/SecurityConfig.java @@ -2,7 +2,7 @@ import dev.gooiman.server.config.security.entrypoint.JwtAuthenticationEntryPoint; import dev.gooiman.server.config.security.handler.JwtAccessDeniedHandler; -import dev.gooiman.server.config.security.provider.JwtAuthenticationProvider; +import dev.gooiman.server.config.security.provider.JwtProvider; import dev.gooiman.server.config.web.CorsConfig; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,7 +24,7 @@ public class SecurityConfig { private final CorsConfig corsConfig; - private final JwtAuthenticationProvider jwtAuthenticationProvider; + private final JwtProvider jwtAuthenticationProvider; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; diff --git a/src/main/java/dev/gooiman/server/config/security/fliter/JwtFilter.java b/src/main/java/dev/gooiman/server/config/security/fliter/JwtFilter.java index 8b527a3..abaa738 100644 --- a/src/main/java/dev/gooiman/server/config/security/fliter/JwtFilter.java +++ b/src/main/java/dev/gooiman/server/config/security/fliter/JwtFilter.java @@ -3,7 +3,7 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.util.StringUtils.hasText; -import dev.gooiman.server.config.security.provider.JwtAuthenticationProvider; +import dev.gooiman.server.config.security.provider.JwtProvider; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -18,9 +18,9 @@ @Slf4j public class JwtFilter extends OncePerRequestFilter { - private final JwtAuthenticationProvider jwtAuthenticationProvider; + private final JwtProvider jwtAuthenticationProvider; - public JwtFilter(JwtAuthenticationProvider jwtAuthenticationProvider) { + public JwtFilter(JwtProvider jwtAuthenticationProvider) { this.jwtAuthenticationProvider = jwtAuthenticationProvider; } diff --git a/src/main/java/dev/gooiman/server/config/security/provider/JwtAuthenticationProvider.java b/src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java similarity index 94% rename from src/main/java/dev/gooiman/server/config/security/provider/JwtAuthenticationProvider.java rename to src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java index 0805051..6ee5577 100644 --- a/src/main/java/dev/gooiman/server/config/security/provider/JwtAuthenticationProvider.java +++ b/src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java @@ -5,9 +5,10 @@ import static dev.gooiman.server.common.exception.ErrorCode.INVALID_TOKEN_ERROR; import static dev.gooiman.server.common.exception.ErrorCode.TOKEN_UNSUPPORTED_ERROR; -import dev.gooiman.server.common.exception.CommonException; +import dev.gooiman.server.auth.application.BlackListService; import dev.gooiman.server.auth.application.CustomUserDetailsService; import dev.gooiman.server.auth.application.domain.CustomUserDetails; +import dev.gooiman.server.common.exception.CommonException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jws; @@ -31,7 +32,7 @@ @Slf4j @Component @RequiredArgsConstructor -public class JwtAuthenticationProvider implements AuthenticationProvider { +public class JwtProvider implements AuthenticationProvider { private static SecretKey key; @@ -39,6 +40,7 @@ public class JwtAuthenticationProvider implements AuthenticationProvider { private String secret; private final CustomUserDetailsService customUserDetailsService; + private final BlackListService blackListService; @PostConstruct public void init() { From d74e87bc2a1037153b5b439854b6342c630852a9 Mon Sep 17 00:00:00 2001 From: koosco Date: Sun, 29 Sep 2024 06:18:33 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20blacklist=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/BlackListService.java | 20 ++++++++++++++++ .../auth/repository/BlackListRepository.java | 8 +++++++ .../auth/repository/entity/BlackList.java | 23 +++++++++++++++++++ src/main/resources/application.yml | 1 + 4 files changed, 52 insertions(+) create mode 100644 src/main/java/dev/gooiman/server/auth/application/BlackListService.java create mode 100644 src/main/java/dev/gooiman/server/auth/repository/BlackListRepository.java create mode 100644 src/main/java/dev/gooiman/server/auth/repository/entity/BlackList.java diff --git a/src/main/java/dev/gooiman/server/auth/application/BlackListService.java b/src/main/java/dev/gooiman/server/auth/application/BlackListService.java new file mode 100644 index 0000000..43b6950 --- /dev/null +++ b/src/main/java/dev/gooiman/server/auth/application/BlackListService.java @@ -0,0 +1,20 @@ +package dev.gooiman.server.auth.application; + +import dev.gooiman.server.auth.repository.BlackListRepository; +import dev.gooiman.server.auth.repository.entity.BlackList; +import dev.gooiman.server.common.dto.CommonSuccessDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class BlackListService { + + private final BlackListRepository blackListRepository; + + public CommonSuccessDto saveBlackList(String token) { + BlackList entity = new BlackList(token); + blackListRepository.save(entity); + return CommonSuccessDto.fromEntity(true); + } +} diff --git a/src/main/java/dev/gooiman/server/auth/repository/BlackListRepository.java b/src/main/java/dev/gooiman/server/auth/repository/BlackListRepository.java new file mode 100644 index 0000000..9e68e53 --- /dev/null +++ b/src/main/java/dev/gooiman/server/auth/repository/BlackListRepository.java @@ -0,0 +1,8 @@ +package dev.gooiman.server.auth.repository; + +import dev.gooiman.server.auth.repository.entity.BlackList; +import org.springframework.data.repository.CrudRepository; + +public interface BlackListRepository extends CrudRepository { + +} diff --git a/src/main/java/dev/gooiman/server/auth/repository/entity/BlackList.java b/src/main/java/dev/gooiman/server/auth/repository/entity/BlackList.java new file mode 100644 index 0000000..b10074e --- /dev/null +++ b/src/main/java/dev/gooiman/server/auth/repository/entity/BlackList.java @@ -0,0 +1,23 @@ +package dev.gooiman.server.auth.repository.entity; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +@Getter +@RedisHash(value = "token") +public class BlackList { + + @Id + private String token; + + @TimeToLive + @Value("${spring.security.blacklist-validity-time}") + private Long expiration; + + public BlackList(String token) { + this.token = token; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 559f8e1..11d8671 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,7 @@ spring: security: secret: ${JWT_SECRET} token-validity-time: ${JWT_TOKEN_VALIDITY_TIME} + blacklist-validity-time: ${BLACKLIST_VALIDITY_TIME} springdoc: packages-to-scan: dev.gooiman.server From 58f8644c2c7594bddfd522c8988023f117bd948c Mon Sep 17 00:00:00 2001 From: koosco Date: Sun, 29 Sep 2024 06:39:03 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20blacklist=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gooiman/server/auth/application/BlackListService.java | 7 +++++++ .../server/config/security/provider/JwtProvider.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/dev/gooiman/server/auth/application/BlackListService.java b/src/main/java/dev/gooiman/server/auth/application/BlackListService.java index 43b6950..6681f66 100644 --- a/src/main/java/dev/gooiman/server/auth/application/BlackListService.java +++ b/src/main/java/dev/gooiman/server/auth/application/BlackListService.java @@ -3,6 +3,7 @@ import dev.gooiman.server.auth.repository.BlackListRepository; import dev.gooiman.server.auth.repository.entity.BlackList; import dev.gooiman.server.common.dto.CommonSuccessDto; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,6 +13,12 @@ public class BlackListService { private final BlackListRepository blackListRepository; + public boolean isExists(String id) { + String bearerToken = "Bearer " + id; + Optional token = blackListRepository.findById(bearerToken); + return token.isPresent(); + } + public CommonSuccessDto saveBlackList(String token) { BlackList entity = new BlackList(token); blackListRepository.save(entity); diff --git a/src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java b/src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java index 6ee5577..5f860a9 100644 --- a/src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java +++ b/src/main/java/dev/gooiman/server/config/security/provider/JwtProvider.java @@ -9,6 +9,7 @@ import dev.gooiman.server.auth.application.CustomUserDetailsService; import dev.gooiman.server.auth.application.domain.CustomUserDetails; import dev.gooiman.server.common.exception.CommonException; +import dev.gooiman.server.common.exception.ErrorCode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jws; @@ -60,6 +61,10 @@ public Authentication authenticate(Authentication authentication) .build() .parseSignedClaims(token); + if (blackListService.isExists(token)) { + throw new CommonException(ErrorCode.INVALID_TOKEN_ERROR); + } + Claims claims = parsedToken.getPayload(); String userId = claims.getSubject();