diff --git a/Jenkinsfile b/Jenkinsfile index 18629db..6e57109 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,7 +13,7 @@ pipeline { parameters { gitParameter branch: '', branchFilter: '.*', - defaultValue: 'origin/main', + defaultValue: 'develop', description: '', listSize: '0', name: 'TAG', quickFilterEnabled: false, @@ -26,15 +26,13 @@ pipeline { } environment { - GIT_URL = "https://github.com/whl5105/UserService.git" + GIT_URL = "https://github.com/PersonalizedNews-MSA/UserService.git" GITHUB_CREDENTIAL = "github-token" ARTIFACTS = "build/libs/**" DOCKER_REGISTRY = "suin4328" DOCKERHUB_CREDENTIAL = 'dockerhub-token' - - KAFKA_BROKER = "${params.KAFKA_BROKER}" // Jenkins UI Parameter 등록 } -₩ + options { disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: "30", artifactNumToKeepStr: "30")) @@ -81,7 +79,7 @@ pipeline { stage('Build & Test Application') { steps { - sh 'export GRADLE_OPTS="-Xmx2g -Dfile.encoding=UTF-8" && gradle clean build' + sh "gradle clean build" } } @@ -93,14 +91,14 @@ pipeline { } } + stage('Push Docker Image') { steps { script { docker.withRegistry("", DOCKERHUB_CREDENTIAL) { - docker.image("${DOCKER_IMAGE_NAME}").push() + sh "docker buildx build --platform linux/amd64,linux/arm64 -t ${DOCKER_IMAGE_NAME} --push ." } - - sh "docker rmi ${DOCKER_IMAGE_NAME}" + sh "docker rmi -f ${DOCKER_IMAGE_NAME}" } } } diff --git a/build.gradle b/build.gradle index 8f03de1..62a3bde 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'com.mini2' -version = '0.0.1' +version = '0.0.2' java { toolchain { @@ -23,6 +23,10 @@ repositories { mavenCentral() } +ext { + set('springCloudVersion', "2025.0.0") +} + dependencies { //jwt implementation 'io.jsonwebtoken:jjwt-api:0.12.5' @@ -46,6 +50,11 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} tasks.named('test') { useJUnitPlatform() diff --git a/src/main/java/com/mini2/user_service/api/BackendK8sController.java b/src/main/java/com/mini2/user_service/api/BackendK8sController.java new file mode 100644 index 0000000..47da865 --- /dev/null +++ b/src/main/java/com/mini2/user_service/api/BackendK8sController.java @@ -0,0 +1,30 @@ +package com.mini2.user_service.api; + +import com.mini2.user_service.common.dto.ApiResponseDto; +import com.mini2.user_service.service.probe.ProbeService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping(value = "/backend/user/v1/k8s", produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +public class BackendK8sController { + private final ProbeService probeService; + + @GetMapping(value = "/liveness") + public ApiResponseDto liveness() { + probeService.validateLiveness(); + return ApiResponseDto.defaultOk(); + } + + @GetMapping(value = "/readiness") + public ApiResponseDto readiness() { + probeService.validateReadiness(); + return ApiResponseDto.defaultOk(); + } +} diff --git a/src/main/java/com/mini2/user_service/api/open/UserAuthController.java b/src/main/java/com/mini2/user_service/api/open/UserAuthController.java index 79b9d77..0949ef8 100644 --- a/src/main/java/com/mini2/user_service/api/open/UserAuthController.java +++ b/src/main/java/com/mini2/user_service/api/open/UserAuthController.java @@ -28,19 +28,21 @@ public class UserAuthController { private final RefreshTokenService refreshTokenService; @Operation(summary = "회원가입", description = "이름, 이메일, 비밀번호를 입력받아 회원가입을 진행합니다.") - @PostMapping(value = "/signup") - public ApiResponseDto register(@RequestBody @Valid UserRegisterRequestDto registerDto) { - userAuthService.registerUser(registerDto); - return ApiResponseDto.defaultOk(); + @PostMapping(value = "/auth/signup") + public ApiResponseDto register(@RequestBody @Valid UserRegisterRequestDto registerDto , HttpServletResponse response) { + String deviceInfo = GatewayRequestHeaderUtils.getClientDeviceOrThrowException(); + TokenDto.AccessRefreshToken token = userAuthService.registerUser(registerDto , deviceInfo); + CookieUtils.addCookie(response, "refreshToken", token.getRefreshToken().getToken(), token.getRefreshToken().getExpiresIn()); + return ApiResponseDto.createOk(new TokenDto.AccessToken(token.getAccessToken())); } @Operation(summary = "사용자 로그인", description = "이메일과 비밀번호를 입력받아 JWT 액세스/리프레시 토큰을 반환합니다.") - @PostMapping(value = "/login") + @PostMapping(value = "/auth/login") public ApiResponseDto login(@RequestBody @Valid UserLoginRequestDto loginDto, HttpServletResponse response , HttpServletRequest request) { String deviceInfo = GatewayRequestHeaderUtils.getClientDeviceOrThrowException(); TokenDto.AccessRefreshToken token = userAuthService.login(loginDto , deviceInfo); - CookieUtils.addCookie(response, "refreshToken", token.getRefresh().getToken(), token.getRefresh().getExpiresIn()); - return ApiResponseDto.createOk(new TokenDto.AccessToken(token.getAccess())); + CookieUtils.addCookie(response, "refreshToken", token.getRefreshToken().getToken(), token.getRefreshToken().getExpiresIn()); + return ApiResponseDto.createOk(new TokenDto.AccessToken(token.getAccessToken())); } @Operation(summary = "AccessToken 재발급", description = "만료된 AccessToken을 리프레시 토큰을 통해 재발급합니다.") @@ -54,10 +56,9 @@ public ApiResponseDto refreshToken(HttpServletRequest requ @Operation(summary = "로그아웃", description = "리프레시 토큰을 비활성화 하고 쿠키 삭제를 통해 로그아웃을 처리합니다.") @PostMapping("/logout") - public ApiResponseDto logout(HttpServletRequest request, HttpServletResponse response) { - String refreshToken = CookieUtils.extractRefreshToken(request); - String deviceInfo = GatewayRequestHeaderUtils.getClientDeviceOrThrowException(); - userAuthService.logout(refreshToken, deviceInfo); + public ApiResponseDto logout(HttpServletResponse response) { + Long userId = Long.valueOf(GatewayRequestHeaderUtils.getUserIdOrThrowException()); + userAuthService.logout(userId); CookieUtils.deleteCookie(response,"refreshToken"); return ApiResponseDto.createOk("로그아웃 되었습니다."); } diff --git a/src/main/java/com/mini2/user_service/api/open/UserController.java b/src/main/java/com/mini2/user_service/api/open/UserController.java index 9cc66f3..c4dcc41 100644 --- a/src/main/java/com/mini2/user_service/api/open/UserController.java +++ b/src/main/java/com/mini2/user_service/api/open/UserController.java @@ -24,7 +24,7 @@ public class UserController { private final UserService userService; @Operation(summary = "이메일 중복 확인", description = "이메일이 이미 존재하는지 확인합니다.") - @PostMapping("/email-check") + @PostMapping("/auth/email-check") public ApiResponseDto checkEmail(@RequestBody @Valid EmailCheckRequestDto emailCheckDto){ boolean isAvailable = userService.isEmailAvailable(emailCheckDto); return ApiResponseDto.createOk(isAvailable); @@ -50,9 +50,8 @@ public ApiResponseDto updateUser( @RequestBody @Valid UserUpdateRequestD @Operation(summary = "회원 탈퇴", description = "현재 로그인된 사용자의 계정을 탈퇴 처리합니다.") @DeleteMapping("/signout") public ApiResponseDto withdraw(HttpServletRequest request, HttpServletResponse response) { - String refreshToken = CookieUtils.extractRefreshToken(request); - String deviceInfo = GatewayRequestHeaderUtils.getClientDeviceOrThrowException(); - userService.withdrawByRequest(refreshToken , deviceInfo); + Long userId = Long.valueOf(GatewayRequestHeaderUtils.getUserIdOrThrowException()); + userService.withdrawByRequest(userId); CookieUtils.deleteCookie(response,"refreshToken"); return ApiResponseDto.createOk("탈퇴 되었습니다."); } diff --git a/src/main/java/com/mini2/user_service/secret/jwt/TokenGenerator.java b/src/main/java/com/mini2/user_service/secret/jwt/TokenGenerator.java index 742523f..d19723c 100644 --- a/src/main/java/com/mini2/user_service/secret/jwt/TokenGenerator.java +++ b/src/main/java/com/mini2/user_service/secret/jwt/TokenGenerator.java @@ -55,7 +55,7 @@ public TokenDto.JwtToken generateJwtToken(Long userId, String deviceType, boolea String token = Jwts.builder() .issuer("welab") .subject(String.valueOf(userId)) - .claim("userId", userId) + .claim("userId", String.valueOf(userId)) .claim("deviceType", deviceType) .claim("tokenType", tokenType) .issuedAt(new Date()) @@ -111,7 +111,7 @@ private Claims verifyAndGetClaims(String token) { //== JWT의 만료 시간(expiration time) 계산 == private int tokenExpiresIn(boolean refreshToken, String deviceType) { if(!refreshToken){ - return 60 * 15; + return 60 * 60; } if(deviceType.equals("MOBILE")){ return configProperties.getMobileExpiresIn(); diff --git a/src/main/java/com/mini2/user_service/secret/jwt/dto/TokenDto.java b/src/main/java/com/mini2/user_service/secret/jwt/dto/TokenDto.java index 41eb0a3..5bceb2d 100644 --- a/src/main/java/com/mini2/user_service/secret/jwt/dto/TokenDto.java +++ b/src/main/java/com/mini2/user_service/secret/jwt/dto/TokenDto.java @@ -17,14 +17,14 @@ public static class JwtToken { @Getter @RequiredArgsConstructor public static class AccessToken { - private final JwtToken access; + private final JwtToken accessToken; } @Getter @Setter @RequiredArgsConstructor public static class AccessRefreshToken { - private final JwtToken access; - private final JwtToken refresh; + private final JwtToken accessToken; + private final JwtToken refreshToken; } } \ No newline at end of file diff --git a/src/main/java/com/mini2/user_service/service/RefreshTokenService.java b/src/main/java/com/mini2/user_service/service/RefreshTokenService.java index 22bb4fe..4351302 100644 --- a/src/main/java/com/mini2/user_service/service/RefreshTokenService.java +++ b/src/main/java/com/mini2/user_service/service/RefreshTokenService.java @@ -75,10 +75,9 @@ public TokenDto.AccessToken reissueAccessToken(String refreshTokenValue, String // 로그아웃 - public void logout(Long userId, String deviceInfo) { - RefreshToken token = refreshTokenRepository.findByUserIdAndDeviceInfo(userId, deviceInfo) + public void logout(Long userId) { + RefreshToken token = refreshTokenRepository.findByUserId(userId) .orElseThrow(() -> new NotFound("해당 사용자의 토큰이 없습니다.")); - token.invalidate(); } diff --git a/src/main/java/com/mini2/user_service/service/UserAuthService.java b/src/main/java/com/mini2/user_service/service/UserAuthService.java index 91e45a3..9beee4c 100644 --- a/src/main/java/com/mini2/user_service/service/UserAuthService.java +++ b/src/main/java/com/mini2/user_service/service/UserAuthService.java @@ -26,7 +26,7 @@ public class UserAuthService { private final RefreshTokenService refreshTokenService; //회원가입 - public void registerUser(UserRegisterRequestDto registerDto) { + public TokenDto.AccessRefreshToken registerUser(UserRegisterRequestDto registerDto ,String deviceInfo ) { String email = registerDto.getEmail().toLowerCase(); Optional optionalUser = userRepository.findByEmail(email); @@ -36,6 +36,11 @@ public void registerUser(UserRegisterRequestDto registerDto) { User user = User.create(email, registerDto.getPassword(), registerDto.getName()); userRepository.save(user); + + TokenDto.AccessRefreshToken token = tokenGenerator.generateAccessRefreshToken(user.getId(), "WEB"); + refreshTokenService.saveToken(user.getId(), token.getRefreshToken() , deviceInfo); + return token; + } // 로그인 @@ -49,19 +54,15 @@ public TokenDto.AccessRefreshToken login(UserLoginRequestDto loginDto, String de throw new BadParameter("아이디 또는 비밀번호를 확인하세요."); } TokenDto.AccessRefreshToken token = tokenGenerator.generateAccessRefreshToken(user.getId(), "WEB"); - refreshTokenService.saveToken(user.getId(), token.getRefresh() , deviceInfo); + refreshTokenService.saveToken(user.getId(), token.getRefreshToken() , deviceInfo); return token; } //로그아웃 - public void logout(String refreshToken, String deviceInfo) { - String userIdStr = tokenGenerator.validateJwtToken(refreshToken); - if (userIdStr == null) { - throw new BadParameter("유효하지 않은 토큰입니다."); - } - Long userId = Long.parseLong(userIdStr); - - refreshTokenService.logout(userId, deviceInfo); + public void logout(Long userId) { + userRepository.findById(userId) + .orElseThrow(() -> new NotFound("유저를 찾을 수 없습니다.")); + refreshTokenService.logout(userId); } diff --git a/src/main/java/com/mini2/user_service/service/UserService.java b/src/main/java/com/mini2/user_service/service/UserService.java index e00585d..4c92d6a 100644 --- a/src/main/java/com/mini2/user_service/service/UserService.java +++ b/src/main/java/com/mini2/user_service/service/UserService.java @@ -47,16 +47,11 @@ public void updateUserInfo(Long userId,UserUpdateRequestDto userDto) { } //회원 탈퇴 - public void withdrawByRequest(String refreshToken ,String deviceInfo) { - String userIdStr = tokenGenerator.validateJwtToken(refreshToken); - - if (userIdStr == null) throw new BadParameter("잘못된 토큰입니다."); - Long userId = Long.parseLong(userIdStr); - + public void withdrawByRequest(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new NotFound("유저를 찾을 수 없습니다.")); user.markAsDeleted(); - refreshTokenService.logout(userId, deviceInfo); + refreshTokenService.logout(userId); } } diff --git a/src/main/java/com/mini2/user_service/service/probe/ProbeService.java b/src/main/java/com/mini2/user_service/service/probe/ProbeService.java new file mode 100644 index 0000000..fa158a7 --- /dev/null +++ b/src/main/java/com/mini2/user_service/service/probe/ProbeService.java @@ -0,0 +1,18 @@ +package com.mini2.user_service.service.probe; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class ProbeService { + + public void validateLiveness() { + // TODO: 만일 서비스를 재시작 해야 하는 상황이면 exception 발생 + } + + public void validateReadiness() { + // TODO: 만일 서비스 수행일 일시 중지해야 하는 상황이면 exception 발생 + } + +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index e69de29..171bd1b 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,5 @@ +spring: + config: + import: + - file:/etc/config/application-dev.yml + - file:/etc/secret/application-secret.yml \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 46f958b..a31d1b6 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,5 +1,5 @@ server: - port: 8080 + port: 8081 spring: datasource: