diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java index 9d2f2caf..8fb6ab3b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java @@ -70,6 +70,10 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { // audit 에는 long 타입 user_id가 들어가지만 토큰 sub은 uuid여서 user_id 가져오기 userid = customUserDetails.getMember().getId(); } + + String requestBody = ApiLogFunction.getRequestBody(servletRequest, contentWrapper); + requestBody = maskSensitiveFields(requestBody); // 로그 저장전에 중요정보 마스킹 + // TODO: menuUid 를 동적으로 가져오게끔 해야함 AuditLogEntity log = new AuditLogEntity( @@ -79,13 +83,33 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { "MU_01_01", ip, servletRequest.getRequestURI(), - ApiLogFunction.getRequestBody(servletRequest, contentWrapper), + requestBody, apiResponse.getErrorLogUid()); - // tb_audit_log 테이블 저장 auditLogRepository.save(log); } return body; } + + /** + * 마스킹 + * + * @param json + * @return + */ + private String maskSensitiveFields(String json) { + if (json == null) { + return null; + } + + // password 마스킹 + json = json.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"****\""); + + // 토큰 마스킹 + json = json.replaceAll("\"accessToken\"\\s*:\\s*\"[^\"]*\"", "\"accessToken\":\"****\""); + json = json.replaceAll("\"refreshToken\"\\s*:\\s*\"[^\"]*\"", "\"refreshToken\":\"****\""); + + return json; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java b/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java index f7a49aa7..776e70ba 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java @@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.members; import com.kamco.cd.kamcoback.auth.JwtTokenProvider; import com.kamco.cd.kamcoback.auth.RefreshTokenService; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.members.dto.SignInRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -9,6 +11,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; +import java.nio.file.AccessDeniedException; import java.time.Duration; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -52,7 +55,7 @@ public class AuthController { description = "ID 또는 비밀번호 불일치", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity signin( + public ApiResponseDto signin( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "로그인 요청 정보", required = true) @@ -61,7 +64,7 @@ public class AuthController { HttpServletResponse response) { Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(request.username(), request.password())); + new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); String username = authentication.getName(); // UserDetailsService 에서 사용한 username @@ -83,8 +86,7 @@ public class AuthController { .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - - return ResponseEntity.ok(new TokenResponse(accessToken)); + return ApiResponseDto.createOK(new TokenResponse(accessToken)); } @PostMapping("/refresh") @@ -99,16 +101,16 @@ public class AuthController { description = "만료되었거나 유효하지 않은 리프레시 토큰", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity refresh(String refreshToken, HttpServletResponse response) { + public ResponseEntity refresh(String refreshToken, HttpServletResponse response) throws AccessDeniedException { if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) { - return ResponseEntity.status(401).build(); + throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); } String username = jwtTokenProvider.getSubject(refreshToken); // Redis에 저장된 RefreshToken과 일치하는지 확인 if (!refreshTokenService.validate(username, refreshToken)) { - return ResponseEntity.status(401).build(); + throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); } // 새 토큰 발급 @@ -141,7 +143,7 @@ public class AuthController { description = "로그아웃 성공", content = @Content(schema = @Schema(implementation = Void.class))) }) - public ResponseEntity logout(Authentication authentication, HttpServletResponse response) { + public ApiResponseDto> logout(Authentication authentication, HttpServletResponse response) { if (authentication != null) { String username = authentication.getName(); // Redis에서 RefreshToken 삭제 @@ -159,14 +161,7 @@ public class AuthController { .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - return ResponseEntity.noContent().build(); - } - - @Schema(description = "로그인 요청 DTO") - public record SignInRequest( - @Schema(description = "사번", example = "11111") String username, - @Schema(description = "비밀번호", example = "kamco1234!") String password) { - + return ApiResponseDto.createOK(ResponseEntity.noContent().build()); } public record TokenResponse(String accessToken) { diff --git a/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java b/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java new file mode 100644 index 00000000..000a51fd --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java @@ -0,0 +1,16 @@ +package com.kamco.cd.kamcoback.members.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(exclude = "password") +public class SignInRequest { + + private String username; + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String password; +}