From 2cbca76b28d0c86b733207f5293fe32efb134e65 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 10:15:20 +0900 Subject: [PATCH 01/12] =?UTF-8?q?KW-33/security,=20jwt,=20redis=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=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 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.gradle b/build.gradle index c581c0d..bbc5b33 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,18 @@ dependencies { implementation 'io.micrometer:micrometer-core' implementation 'io.micrometer:micrometer-registry-prometheus' + // Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-test' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.h2database:h2' From 4197d3f863831f12e481656af72b2e27f806a696 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:12:42 +0900 Subject: [PATCH 02/12] =?UTF-8?q?KW-33/feat:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EA=B4=80=EB=A6=AC=20=EC=9C=84=ED=95=9C=20propertie?= =?UTF-8?q?s=20config=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/config/jwt/JwtProperties.java | 19 +++++++++++ .../config/properties/PropertiesConfig.java | 10 ++++++ .../infra/config/redis/RedisConfig.java | 34 +++++++++++++++++++ .../infra/config/redis/RedisProperties.java | 6 ++++ 4 files changed, 69 insertions(+) create mode 100644 src/main/java/com/doubleo/adminservice/infra/config/jwt/JwtProperties.java create mode 100644 src/main/java/com/doubleo/adminservice/infra/config/properties/PropertiesConfig.java create mode 100644 src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java create mode 100644 src/main/java/com/doubleo/adminservice/infra/config/redis/RedisProperties.java diff --git a/src/main/java/com/doubleo/adminservice/infra/config/jwt/JwtProperties.java b/src/main/java/com/doubleo/adminservice/infra/config/jwt/JwtProperties.java new file mode 100644 index 0000000..4b92773 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/infra/config/jwt/JwtProperties.java @@ -0,0 +1,19 @@ +package com.doubleo.adminservice.infra.config.jwt; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt") +public record JwtProperties( + String accessTokenSecret, + String refreshTokenSecret, + Long accessTokenExpirationTime, + Long refreshTokenExpirationTime, + String issuer) { + public Long accessTokenExpirationMilliTime() { + return accessTokenExpirationTime * 1000; + } + + public Long refreshTokenExpirationMilliTime() { + return refreshTokenExpirationTime * 1000; + } +} diff --git a/src/main/java/com/doubleo/adminservice/infra/config/properties/PropertiesConfig.java b/src/main/java/com/doubleo/adminservice/infra/config/properties/PropertiesConfig.java new file mode 100644 index 0000000..5e9b93b --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/infra/config/properties/PropertiesConfig.java @@ -0,0 +1,10 @@ +package com.doubleo.adminservice.infra.config.properties; + +import com.doubleo.adminservice.infra.config.jwt.JwtProperties; +import com.doubleo.adminservice.infra.config.redis.RedisProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@EnableConfigurationProperties({JwtProperties.class, RedisProperties.class}) +@Configuration +public class PropertiesConfig {} diff --git a/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java b/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java new file mode 100644 index 0000000..be7e262 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java @@ -0,0 +1,34 @@ +package com.doubleo.adminservice.infra.config.redis; + +import java.time.Duration; +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.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +@Configuration +@RequiredArgsConstructor +public class RedisConfig { + + private final RedisProperties redisProperties; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfig = + new RedisStandaloneConfiguration(redisProperties.host(), redisProperties.port()); + + if (!redisProperties.password().isBlank()) + redisStandaloneConfig.setPassword(redisProperties.password()); + + LettuceClientConfiguration lettuceClientConfig = + LettuceClientConfiguration.builder() + .commandTimeout(Duration.ofSeconds(1)) + .shutdownTimeout(Duration.ZERO) + .build(); + + return new LettuceConnectionFactory(redisStandaloneConfig, lettuceClientConfig); + } +} diff --git a/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisProperties.java b/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisProperties.java new file mode 100644 index 0000000..4921cbd --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisProperties.java @@ -0,0 +1,6 @@ +package com.doubleo.adminservice.infra.config.redis; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.data.redis") +public record RedisProperties(String host, int port, String password) {} From d4571fc29f76eeacca4d6db1e6a9778e405d23bd Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:14:00 +0900 Subject: [PATCH 03/12] =?UTF-8?q?KW-33/feat:=20security=20filter=20chain?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/WebSecurityConfig.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/com/doubleo/adminservice/global/config/security/WebSecurityConfig.java diff --git a/src/main/java/com/doubleo/adminservice/global/config/security/WebSecurityConfig.java b/src/main/java/com/doubleo/adminservice/global/config/security/WebSecurityConfig.java new file mode 100644 index 0000000..d3df86d --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/global/config/security/WebSecurityConfig.java @@ -0,0 +1,36 @@ +package com.doubleo.adminservice.global.config.security; + +import static org.springframework.security.config.Customizer.withDefaults; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class WebSecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) + .cors(withDefaults()) + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()); + return http.build(); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} From 090bc1172f56feee7323e44526a5ecc435d54d1d Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:15:41 +0900 Subject: [PATCH 04/12] =?UTF-8?q?KW-33/feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8/=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 97 +++++++++++++++++++ .../domain/auth/domain/BlackListToken.java | 27 ++++++ .../domain/auth/domain/RefreshToken.java | 25 +++++ .../domain/auth/dto/AccessTokenDto.java | 7 ++ .../domain/auth/dto/RefreshTokenDto.java | 7 ++ .../domain/auth/dto/request/LoginRequest.java | 7 ++ .../auth/dto/response/LoginResponse.java | 9 ++ .../repository/BlackListTokenRepository.java | 6 ++ .../repository/RefreshTokenRepository.java | 6 ++ .../domain/auth/service/AuthService.java | 11 +++ .../domain/auth/service/AuthServiceImpl.java | 54 +++++++++++ .../domain/auth/service/JwtTokenService.java | 25 +++++ .../auth/service/JwtTokenServiceImpl.java | 97 +++++++++++++++++++ 13 files changed, 378 insertions(+) create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/controller/AuthController.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/domain/BlackListToken.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/domain/RefreshToken.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/dto/RefreshTokenDto.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/dto/response/LoginResponse.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/repository/BlackListTokenRepository.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/repository/RefreshTokenRepository.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/service/AuthService.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java create mode 100644 src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/controller/AuthController.java b/src/main/java/com/doubleo/adminservice/domain/auth/controller/AuthController.java new file mode 100644 index 0000000..a8e7d67 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/controller/AuthController.java @@ -0,0 +1,97 @@ +package com.doubleo.adminservice.domain.auth.controller; + +import com.doubleo.adminservice.domain.auth.dto.AccessTokenDto; +import com.doubleo.adminservice.domain.auth.dto.RefreshTokenDto; +import com.doubleo.adminservice.domain.auth.dto.request.LoginRequest; +import com.doubleo.adminservice.domain.auth.dto.response.LoginResponse; +import com.doubleo.adminservice.domain.auth.service.AuthService; +import com.doubleo.adminservice.domain.auth.service.JwtTokenService; +import com.doubleo.adminservice.global.util.CookieUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.WebUtils; + +@Tag(name = "1-2. Auth API", description = "회원 로그인/로그아웃/Refresh Token 관련 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth") +public class AuthController { + + private final AuthService authService; + private final CookieUtil cookieUtil; + private final JwtTokenService jwtTokenService; + + @Operation(summary = "회원 로그인", description = "회원 로그인을 처리합니다.") + @PostMapping("/login") + public ResponseEntity adminLogin(@RequestBody LoginRequest request) { + LoginResponse response = authService.loginAdmin(request); + String refreshToken = response.refreshToken(); + HttpHeaders headers = cookieUtil.generateRefreshTokenCookie(refreshToken); + + return ResponseEntity.ok().headers(headers).body(response); + } + + @Operation(summary = "회원 로그아웃", description = "회원 로그아웃을 처리합니다.") + @PostMapping("/logout") + public ResponseEntity adminLogout( + @RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader, + @RequestHeader("X-Admin-Id") Long adminId, + HttpServletResponse response) { + authService.logoutAdmin(authorizationHeader, adminId); + + ResponseCookie clearCookie = + ResponseCookie.from("refreshToken", "") + .httpOnly(true) + .secure(true) + .path("/") + .maxAge(0) + .sameSite("Strict") + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, clearCookie.toString()); + + return ResponseEntity.noContent().build(); + } + + @Operation( + summary = "Access Token 재발급", + description = "유효한 RefreshToken 을 통해 AccessToken 을 재발급합니다.") + @PostMapping("/reissue") + public ResponseEntity tokenReissue( + HttpServletRequest request, HttpServletResponse response) { + String oldAccessToken = extractAccessTokenFromHeader(request); + String refreshToken = extractRefreshTokenFromCookie(request); + + RefreshTokenDto refreshTokenDto = jwtTokenService.retrieveRefreshToken(refreshToken); + if (refreshTokenDto == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + AccessTokenDto newAccessTokenDto = + jwtTokenService.reissueAccessTokenIfExpired(oldAccessToken); + response.addHeader( + HttpHeaders.AUTHORIZATION, "Bearer " + newAccessTokenDto.accessTokenValue()); + + return ResponseEntity.ok().build(); + } + + private String extractRefreshTokenFromCookie(HttpServletRequest request) { + Cookie cookie = WebUtils.getCookie(request, "refreshToken"); + return (cookie != null) ? cookie.getValue() : null; + } + + private String extractAccessTokenFromHeader(HttpServletRequest request) { + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + return authorizationHeader.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/domain/BlackListToken.java b/src/main/java/com/doubleo/adminservice/domain/auth/domain/BlackListToken.java new file mode 100644 index 0000000..b9edf25 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/domain/BlackListToken.java @@ -0,0 +1,27 @@ +package com.doubleo.adminservice.domain.auth.domain; + +import lombok.AccessLevel; +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("blacklistToken") +public class BlackListToken { + + @Id private final String token; + + @TimeToLive private final long ttl; + + @Builder(access = AccessLevel.PRIVATE) + private BlackListToken(String token, long ttl) { + this.token = token; + this.ttl = ttl; + } + + public static BlackListToken createBlackListToken(String token, Long ttl) { + return builder().token(token).ttl(ttl).build(); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/domain/RefreshToken.java b/src/main/java/com/doubleo/adminservice/domain/auth/domain/RefreshToken.java new file mode 100644 index 0000000..d99030a --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/domain/RefreshToken.java @@ -0,0 +1,25 @@ +package com.doubleo.adminservice.domain.auth.domain; + +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(value = "refreshToken") +public class RefreshToken { + + @Id private final Long adminId; + + private final String token; + + @TimeToLive private final long ttl; + + @Builder + private RefreshToken(Long adminId, String token, long ttl) { + this.adminId = adminId; + this.token = token; + this.ttl = ttl; + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java b/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java new file mode 100644 index 0000000..2ab095e --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java @@ -0,0 +1,7 @@ +package com.doubleo.adminservice.domain.auth.dto; + +public record AccessTokenDto(Long adminId, String accessTokenValue) { + public static AccessTokenDto of(Long adminId, String accessTokenValue) { + return new AccessTokenDto(adminId, accessTokenValue); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/dto/RefreshTokenDto.java b/src/main/java/com/doubleo/adminservice/domain/auth/dto/RefreshTokenDto.java new file mode 100644 index 0000000..446f43a --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/dto/RefreshTokenDto.java @@ -0,0 +1,7 @@ +package com.doubleo.adminservice.domain.auth.dto; + +public record RefreshTokenDto(Long adminId, String refreshTokenValue, Long ttl) { + public static RefreshTokenDto of(Long adminId, String refreshTokenValue, Long ttl) { + return new RefreshTokenDto(adminId, refreshTokenValue, ttl); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java b/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java new file mode 100644 index 0000000..f182978 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java @@ -0,0 +1,7 @@ +package com.doubleo.adminservice.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record LoginRequest( + @Schema(description = "관리자 email", example = "example@gmail.com") String email, + @Schema(description = "관리자 패스워드", example = "pw12345") String password) {} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/dto/response/LoginResponse.java b/src/main/java/com/doubleo/adminservice/domain/auth/dto/response/LoginResponse.java new file mode 100644 index 0000000..426f73a --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/dto/response/LoginResponse.java @@ -0,0 +1,9 @@ +package com.doubleo.adminservice.domain.auth.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public record LoginResponse(String accessToken, @JsonIgnore String refreshToken) { + public static LoginResponse of(String accessToken, String refreshToken) { + return new LoginResponse(accessToken, refreshToken); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/repository/BlackListTokenRepository.java b/src/main/java/com/doubleo/adminservice/domain/auth/repository/BlackListTokenRepository.java new file mode 100644 index 0000000..64ce393 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/repository/BlackListTokenRepository.java @@ -0,0 +1,6 @@ +package com.doubleo.adminservice.domain.auth.repository; + +import com.doubleo.adminservice.domain.auth.domain.BlackListToken; +import org.springframework.data.repository.CrudRepository; + +public interface BlackListTokenRepository extends CrudRepository {} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/repository/RefreshTokenRepository.java b/src/main/java/com/doubleo/adminservice/domain/auth/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..ac4b56d --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/repository/RefreshTokenRepository.java @@ -0,0 +1,6 @@ +package com.doubleo.adminservice.domain.auth.repository; + +import com.doubleo.adminservice.domain.auth.domain.RefreshToken; +import org.springframework.data.repository.CrudRepository; + +public interface RefreshTokenRepository extends CrudRepository {} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthService.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthService.java new file mode 100644 index 0000000..3dfeb39 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthService.java @@ -0,0 +1,11 @@ +package com.doubleo.adminservice.domain.auth.service; + +import com.doubleo.adminservice.domain.auth.dto.request.LoginRequest; +import com.doubleo.adminservice.domain.auth.dto.response.LoginResponse; + +public interface AuthService { + + LoginResponse loginAdmin(LoginRequest request); + + void logoutAdmin(String accessTokenValue, Long adminId); +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java new file mode 100644 index 0000000..3b43a0b --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java @@ -0,0 +1,54 @@ +package com.doubleo.adminservice.domain.auth.service; + +import com.doubleo.adminservice.domain.admin.domain.Admin; +import com.doubleo.adminservice.domain.admin.repository.AdminRepository; +import com.doubleo.adminservice.domain.auth.dto.request.LoginRequest; +import com.doubleo.adminservice.domain.auth.dto.response.LoginResponse; +import com.doubleo.adminservice.domain.auth.repository.RefreshTokenRepository; +import com.doubleo.adminservice.global.exception.CommonException; +import com.doubleo.adminservice.global.exception.errorcode.AdminErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + + private final AdminRepository adminRepository; + private final RefreshTokenRepository refreshTokenRepository; + private final JwtTokenService jwtTokenService; + private final BCryptPasswordEncoder encoder; + + public LoginResponse loginAdmin(LoginRequest request) { + Admin admin = validateAdminByEmail(request.email()); + if (!encoder.matches(request.password(), admin.getPassword())) { + throw new CommonException(AdminErrorCode.ADMIN_NOT_FOUND); + } + return getLoginResponse(admin); + } + + public void logoutAdmin(String accessTokenValue, Long adminId) { + validateAdminById(adminId); + refreshTokenRepository.deleteById(adminId); + jwtTokenService.putAccessTokenOnBlackList(accessTokenValue); + } + + private Admin validateAdminByEmail(String email) { + return adminRepository + .findAdminByUsername(email) + .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND)); + } + + private void validateAdminById(Long adminId) { + adminRepository + .findById(adminId) + .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND)); + } + + private LoginResponse getLoginResponse(Admin admin) { + String accessToken = jwtTokenService.createAccessToken(admin.getId()); + String refreshToken = jwtTokenService.createRefreshToken(admin.getId()); + return LoginResponse.of(accessToken, refreshToken); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java new file mode 100644 index 0000000..3ac5888 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java @@ -0,0 +1,25 @@ +package com.doubleo.adminservice.domain.auth.service; + +import com.doubleo.adminservice.domain.auth.dto.AccessTokenDto; +import com.doubleo.adminservice.domain.auth.dto.RefreshTokenDto; + +public interface JwtTokenService { + + // AccessToken DTO 생성 + AccessTokenDto createAccessTokenDto(Long adminId); + + // AccessToken 생성 + String createAccessToken(Long adminId); + + // RefreshToken 생성 + String createRefreshToken(Long adminId); + + // DB 저장된 RefreshToken 조회 및 검증 + RefreshTokenDto retrieveRefreshToken(String refreshTokenValue); + + // AccessToken 만료 여부 검증 후 재발급 + AccessTokenDto reissueAccessTokenIfExpired(String accessTokenValue); + + // 사용하지 않는 AccessToken BlackList 등록 + void putAccessTokenOnBlackList(String accessTokenValue); +} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java new file mode 100644 index 0000000..01e09cf --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java @@ -0,0 +1,97 @@ +package com.doubleo.adminservice.domain.auth.service; + +import com.doubleo.adminservice.domain.auth.domain.BlackListToken; +import com.doubleo.adminservice.domain.auth.domain.RefreshToken; +import com.doubleo.adminservice.domain.auth.dto.AccessTokenDto; +import com.doubleo.adminservice.domain.auth.dto.RefreshTokenDto; +import com.doubleo.adminservice.domain.auth.repository.BlackListTokenRepository; +import com.doubleo.adminservice.domain.auth.repository.RefreshTokenRepository; +import com.doubleo.adminservice.global.util.JwtUtil; +import io.jsonwebtoken.ExpiredJwtException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class JwtTokenServiceImpl implements JwtTokenService { + + private final JwtUtil jwtUtil; + private final RefreshTokenRepository refreshTokenRepository; + private final BlackListTokenRepository blackListTokenRepository; + + public AccessTokenDto createAccessTokenDto(Long adminId) { + return jwtUtil.generateAccessTokenDto(adminId); + } + + public String createAccessToken(Long adminId) { + return jwtUtil.generateAccessToken(adminId); + } + + public String createRefreshToken(Long adminId) { + String token = jwtUtil.generateRefreshToken(adminId); + RefreshToken refreshToken = + RefreshToken.builder() + .adminId(adminId) + .token(token) + .ttl(jwtUtil.getRefreshTokenExpirationTime()) + .build(); + refreshTokenRepository.save(refreshToken); + + return token; + } + + public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) { + RefreshTokenDto refreshTokenDto = parseRefreshToken(refreshTokenValue); + + if (refreshTokenDto == null) { + return null; + } + + Optional refreshToken = getRefreshToken(refreshTokenDto.adminId()); + + if (refreshToken.isPresent() + && refreshTokenDto.refreshTokenValue().equals(refreshToken.get().getToken())) { + return refreshTokenDto; + } + + return null; + } + + public AccessTokenDto reissueAccessTokenIfExpired(String accessTokenValue) { + try { + jwtUtil.parseAccessToken(accessTokenValue); + return null; + } catch (ExpiredJwtException e) { + Long adminId = Long.parseLong(e.getClaims().getSubject()); + + return createAccessTokenDto(adminId); + } + } + + public void putAccessTokenOnBlackList(String accessTokenValue) { + + String accessToken = jwtUtil.resolveToken(accessTokenValue); + if (accessToken == null) { + return; + } + + long remainingMs = jwtUtil.getRemainingExpirationMillis(accessToken); + long ttlSeconds = remainingMs > 0 ? remainingMs / 1000 : 0; + + BlackListToken black = BlackListToken.createBlackListToken(accessToken, ttlSeconds); + blackListTokenRepository.save(black); + } + + private RefreshTokenDto parseRefreshToken(String refreshTokenValue) { + try { + return jwtUtil.parseRefreshToken(refreshTokenValue); + } catch (Exception e) { + return null; + } + } + + private Optional getRefreshToken(Long adminId) { + return refreshTokenRepository.findById(adminId); + } +} From 5b5c8e69c4324b1535acd44cae910348afc3bb2b Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:16:23 +0900 Subject: [PATCH 05/12] =?UTF-8?q?KW-33/feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20ErrorCode=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/exception/errorcode/AdminErrorCode.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/doubleo/adminservice/global/exception/errorcode/AdminErrorCode.java b/src/main/java/com/doubleo/adminservice/global/exception/errorcode/AdminErrorCode.java index 6082d64..1550273 100644 --- a/src/main/java/com/doubleo/adminservice/global/exception/errorcode/AdminErrorCode.java +++ b/src/main/java/com/doubleo/adminservice/global/exception/errorcode/AdminErrorCode.java @@ -8,7 +8,9 @@ @AllArgsConstructor public enum AdminErrorCode implements BaseErrorCode { ADMIN_NOT_FOUND(HttpStatus.NOT_FOUND, "관리자 정보를 찾을 수 없습니다."), - ; + ADMIN_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 관리자입니다."), + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "유효한 비밀번호가 아닙니다."), + DUPLICATED_PASSWORD(HttpStatus.BAD_REQUEST, "변경되는 비밀번호는 기존 비밀번호와 동일할 수 없습니다."); private final HttpStatus httpStatus; private final String message; From 7ced5c6250056916f969aeca4fec873eb98915e8 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:17:22 +0900 Subject: [PATCH 06/12] =?UTF-8?q?KW-33/feat:=20application.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(redis=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=93=B1=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 --- src/main/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3b730e8..cedbb3e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,5 +17,6 @@ spring: - swagger - openfeign - datasource + - redis - eureka - actuator From 9112e7800cce9611dd66376fe608773ef6479875 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:29:06 +0900 Subject: [PATCH 07/12] =?UTF-8?q?KW-33/refactor:=20test=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20redis=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adminservice/AdminServiceApplicationTests.java | 2 ++ src/test/resources/application-test.yml | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java b/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java index 838ab60..43b1f61 100644 --- a/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java +++ b/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java @@ -2,8 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest +@ActiveProfiles({"test", "redis", "security"}) class AdminServiceApplicationTests { @Test diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 21d0d11..03eac81 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -1,6 +1,13 @@ spring: config: activate: - on-profile: "test" + on-profile: test + datasource: - url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MYSQL + url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MYSQL + + data: + redis: + host: localhost + port: 6379 + password: \ No newline at end of file From a366812301cada235f33bdcf4296332db881c5e8 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:36:17 +0900 Subject: [PATCH 08/12] =?UTF-8?q?KW-33/refactor:=20redis=20utility=20?= =?UTF-8?q?=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adminservice/global/util/CookieUtil.java | 41 ++++++ .../adminservice/global/util/JwtUtil.java | 134 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 src/main/java/com/doubleo/adminservice/global/util/CookieUtil.java create mode 100644 src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java diff --git a/src/main/java/com/doubleo/adminservice/global/util/CookieUtil.java b/src/main/java/com/doubleo/adminservice/global/util/CookieUtil.java new file mode 100644 index 0000000..13263a2 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/global/util/CookieUtil.java @@ -0,0 +1,41 @@ +package com.doubleo.adminservice.global.util; + +import org.springframework.boot.web.server.Cookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +@Component +public class CookieUtil { + + public HttpHeaders generateRefreshTokenCookie(String refreshToken) { + ResponseCookie refreshTokenCookie = + ResponseCookie.from("refreshToken", refreshToken) + .path("/") + .secure(false) + .sameSite(Cookie.SameSite.NONE.attributeValue()) + .httpOnly(true) + .build(); + + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); + + return headers; + } + + public HttpHeaders deleteRefreshTokenCookie() { + ResponseCookie refreshTokenCookie = + ResponseCookie.from("refreshToken", "") + .path("/") + .maxAge(0) + .secure(false) + .sameSite(Cookie.SameSite.NONE.attributeValue()) + .httpOnly(true) + .build(); + + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); + + return headers; + } +} diff --git a/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java b/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java new file mode 100644 index 0000000..aad7457 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java @@ -0,0 +1,134 @@ +package com.doubleo.adminservice.global.util; + +import com.doubleo.adminservice.domain.auth.dto.AccessTokenDto; +import com.doubleo.adminservice.domain.auth.dto.RefreshTokenDto; +import com.doubleo.adminservice.infra.config.jwt.JwtProperties; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.security.Key; +import java.util.Date; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class JwtUtil { + + private final JwtProperties jwtProperties; + + public AccessTokenDto generateAccessTokenDto(Long adminId) { + Date issuedAt = new Date(); + Date expiredAt = + new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + String tokenValue = buildAccessToken(adminId, issuedAt, expiredAt); + return new AccessTokenDto(adminId, tokenValue); + } + + public String generateAccessToken(Long adminId) { + Date issuedAt = new Date(); + Date expiredAt = + new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + return buildAccessToken(adminId, issuedAt, expiredAt); + } + + // public RefreshTokenDto generateRefreshTokenDto(Long adminId) { + // Date issuedAt = new Date(); + // Date expiredAt = + // new Date(issuedAt.getTime() + + // jwtProperties.refreshTokenExpirationMilliTime()); + // String tokenValue = buildRefreshToken(adminId, issuedAt, expiredAt); + // return new RefreshTokenDto( + // adminId, tokenValue, jwtProperties.refreshTokenExpirationTime()); + // } + + public String resolveToken(String headerValue) { + if (headerValue != null && headerValue.startsWith("Bearer ")) { + return headerValue.substring(7); + } + return null; + } + + public long getRemainingExpirationMillis(String tokenValue) { + Jws claims = getClaims(tokenValue, getAccessTokenKey()); + Date exp = claims.getBody().getExpiration(); + return Math.max(exp.getTime() - System.currentTimeMillis(), 0); + } + + public AccessTokenDto parseAccessToken(String accessTokenValue) throws ExpiredJwtException { + try { + Jws claims = getClaims(accessTokenValue, getAccessTokenKey()); + + return AccessTokenDto.of( + Long.parseLong(claims.getBody().getSubject()), accessTokenValue); + } catch (ExpiredJwtException e) { + throw e; + } catch (Exception e) { + return null; + } + } + + public RefreshTokenDto parseRefreshToken(String refreshTokenValue) throws ExpiredJwtException { + try { + Jws claims = getClaims(refreshTokenValue, getRefreshTokenKey()); + + return RefreshTokenDto.of( + Long.parseLong(claims.getBody().getSubject()), + refreshTokenValue, + jwtProperties.refreshTokenExpirationTime()); + } catch (ExpiredJwtException e) { + throw e; + } catch (Exception e) { + return null; + } + } + + public String generateRefreshToken(Long adminId) { + Date issuedAt = new Date(); + Date expiredAt = + new Date(issuedAt.getTime() + jwtProperties.refreshTokenExpirationMilliTime()); + return buildRefreshToken(adminId, issuedAt, expiredAt); + } + + public long getRefreshTokenExpirationTime() { + return jwtProperties.refreshTokenExpirationTime(); + } + + private Key getAccessTokenKey() { + return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); + } + + private Key getRefreshTokenKey() { + return Keys.hmacShaKeyFor(jwtProperties.refreshTokenSecret().getBytes()); + } + + private String buildAccessToken(Long adminId, Date issuedAt, Date expiredAt) { + return Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject(adminId.toString()) + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(getAccessTokenKey()) + .compact(); + } + + private String buildRefreshToken(Long adminId, Date issuedAt, Date expiredAt) { + return Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject(adminId.toString()) + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(getRefreshTokenKey()) + .compact(); + } + + private Jws getClaims(String token, Key key) { + return Jwts.parserBuilder() + .requireIssuer(jwtProperties.issuer()) + .setSigningKey(key) + .build() + .parseClaimsJws(token); + } +} From 60c4058c212f79cf87ddff885a1a9d087bba2776 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:40:12 +0900 Subject: [PATCH 09/12] =?UTF-8?q?KW-33/refactor:=20adminRepository=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/admin/repository/AdminRepository.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/doubleo/adminservice/domain/admin/repository/AdminRepository.java diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/repository/AdminRepository.java b/src/main/java/com/doubleo/adminservice/domain/admin/repository/AdminRepository.java new file mode 100644 index 0000000..aa10729 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/admin/repository/AdminRepository.java @@ -0,0 +1,11 @@ +package com.doubleo.adminservice.domain.admin.repository; + +import com.doubleo.adminservice.domain.admin.domain.Admin; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AdminRepository extends JpaRepository { + Optional findAdminByUsername(String username); +} From 480d95a6edc3f04efac98656a8305cfaf7d9c5a2 Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:44:30 +0900 Subject: [PATCH 10/12] =?UTF-8?q?KW-33/refactor:=20admin=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/admin/domain/Admin.java | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java b/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java index f433f6e..bed2459 100644 --- a/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java +++ b/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java @@ -11,28 +11,42 @@ @Getter @NoArgsConstructor public class Admin extends BaseTimeEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "admin_id") + @Column(name = "admin_id", nullable = false) private Long id; - @Column(name = "admin_company") - private String company; - - @Column(name = "admin_account_id") - private String accountId; // 정규화 추가 + @Column(name = "admin_username", nullable = false, unique = true) + private String username; // 정규화 추가 - @Column(name = "admin_password") + @Column(name = "admin_password", nullable = false, length = 100) private String password; + @Column(name = "admin_name", nullable = false) + private String name; + + @Column(name = "admin_contact", nullable = false) + private String contact; + @Builder(access = AccessLevel.PRIVATE) - private Admin(String company, String accountId, String password) { - this.company = company; - this.accountId = accountId; + private Admin(String username, String password, String name, String contact) { + this.username = username; this.password = password; + this.name = name; + this.contact = contact; + } + + public static Admin createAdmin(String username, String password, String name, String contact) { + return Admin.builder() + .username(username) + .password(password) + .name(name) + .contact(contact) + .build(); } - public static Admin createAdmin(String company, String accountId, String password) { - return Admin.builder().company(company).accountId(accountId).password(password).build(); + public void updateAdminPassword(String passwordNew) { + this.password = passwordNew; } } From ea14b05134e64dec697f57b5b3644df3d4f192ef Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 11:48:18 +0900 Subject: [PATCH 11/12] KW-33/fix: CI test --- .../com/doubleo/adminservice/domain/admin/domain/Admin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java b/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java index bed2459..8721aaa 100644 --- a/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java +++ b/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java @@ -18,7 +18,7 @@ public class Admin extends BaseTimeEntity { private Long id; @Column(name = "admin_username", nullable = false, unique = true) - private String username; // 정규화 추가 + private String username; @Column(name = "admin_password", nullable = false, length = 100) private String password; From edec1dc11cace605027959fba63d30033d444fba Mon Sep 17 00:00:00 2001 From: willjsw Date: Sat, 3 May 2025 15:35:31 +0900 Subject: [PATCH 12/12] =?UTF-8?q?KW-33/fix:=20PR=20Review=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81(email->=20user?= =?UTF-8?q?name,=20if=EB=AC=B8=20=EC=A4=91=EA=B4=84=ED=98=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adminservice/domain/auth/dto/request/LoginRequest.java | 2 +- .../adminservice/domain/auth/service/AuthServiceImpl.java | 2 +- .../doubleo/adminservice/infra/config/redis/RedisConfig.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java b/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java index f182978..52e741f 100644 --- a/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java +++ b/src/main/java/com/doubleo/adminservice/domain/auth/dto/request/LoginRequest.java @@ -3,5 +3,5 @@ import io.swagger.v3.oas.annotations.media.Schema; public record LoginRequest( - @Schema(description = "관리자 email", example = "example@gmail.com") String email, + @Schema(description = "관리자 아이디", example = "username") String username, @Schema(description = "관리자 패스워드", example = "pw12345") String password) {} diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java index 3b43a0b..bc20c2b 100644 --- a/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java @@ -21,7 +21,7 @@ public class AuthServiceImpl implements AuthService { private final BCryptPasswordEncoder encoder; public LoginResponse loginAdmin(LoginRequest request) { - Admin admin = validateAdminByEmail(request.email()); + Admin admin = validateAdminByEmail(request.username()); if (!encoder.matches(request.password(), admin.getPassword())) { throw new CommonException(AdminErrorCode.ADMIN_NOT_FOUND); } diff --git a/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java b/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java index be7e262..7864866 100644 --- a/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java +++ b/src/main/java/com/doubleo/adminservice/infra/config/redis/RedisConfig.java @@ -20,8 +20,9 @@ public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration redisStandaloneConfig = new RedisStandaloneConfiguration(redisProperties.host(), redisProperties.port()); - if (!redisProperties.password().isBlank()) + if (!redisProperties.password().isBlank()) { redisStandaloneConfig.setPassword(redisProperties.password()); + } LettuceClientConfiguration lettuceClientConfig = LettuceClientConfiguration.builder()