diff --git a/src/main/java/life/mosu/mosuserver/application/event/EventService.java b/src/main/java/life/mosu/mosuserver/application/event/EventService.java index 0fb020ed..7cf4bf8a 100644 --- a/src/main/java/life/mosu/mosuserver/application/event/EventService.java +++ b/src/main/java/life/mosu/mosuserver/application/event/EventService.java @@ -7,6 +7,7 @@ import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.global.support.CursorResponse; +import life.mosu.mosuserver.infra.persistence.s3.FileUploadHelper; import life.mosu.mosuserver.infra.persistence.s3.S3Service; import life.mosu.mosuserver.presentation.event.dto.EventRequest; import life.mosu.mosuserver.presentation.event.dto.EventResponse; @@ -22,12 +23,13 @@ public class EventService { private final EventJpaRepository eventJpaRepository; private final EventQueryRepository eventQueryRepository; - // private final EventAttachmentService attachmentService; + private final FileUploadHelper uploadHelper; private final S3Service s3Service; @Transactional public void createEvent(EventRequest request) { EventJpaEntity eventEntity = eventJpaRepository.save(request.toEntity()); + uploadHelper.updateTag(eventEntity.getS3Key()); // attachmentService.createAttachment(request.optionalAttachment(), eventEntity); } diff --git a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java index 8fd8d1ef..bde22bd6 100644 --- a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java @@ -153,7 +153,7 @@ public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicat examApplicationInfo.schoolName(), AddressResponse.from(examApplicationInfo.address()), subjects, - examApplicationInfo.isLunchChecked() ? examApplicationInfo.lunchName() : "신청 안 함", + examApplicationInfo.isLunchChecked() ? examApplicationInfo.lunchName() : "도시락 X", paymentAmount, discountAmount, examApplicationInfo.paymentMethod().getName() diff --git a/src/main/java/life/mosu/mosuserver/domain/application/entity/Lunch.java b/src/main/java/life/mosu/mosuserver/domain/application/entity/Lunch.java index 835f24e6..2ff07bba 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/entity/Lunch.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/entity/Lunch.java @@ -9,7 +9,7 @@ @Getter @RequiredArgsConstructor public enum Lunch { - NONE("선택 안 함"), + NONE("도시락 X"), OPTION1("도시락 A"), OPTION2("도시락 B"), OPTION3("비건 도시락"), diff --git a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java index e9918494..12499a2a 100644 --- a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java +++ b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java @@ -32,6 +32,7 @@ public enum ErrorCode { INVALID_SIGN_UP_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 회원가입 인증 토큰입니다."), MISSING_SIGNUP_TOKEN(HttpStatus.BAD_REQUEST, "회원가입 인증 토큰이 누락되었습니다."), MISSING_PASSWORD_TOKEN(HttpStatus.BAD_REQUEST, "비밀번호 변경 토큰이 누락되었습니다."), + COOKIE_NOT_FOUND(HttpStatus.NOT_FOUND, "쿠키가 존재하지 않습니다."), NOT_FOUND_TOKEN(HttpStatus.NOT_FOUND, "인증 토큰을 찾을 수 없습니다."), NOT_FOUND_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "액세스 토큰을 찾을 수 없습니다."), diff --git a/src/main/java/life/mosu/mosuserver/global/filter/AuthConstants.java b/src/main/java/life/mosu/mosuserver/global/filter/AuthConstants.java index a2e36e42..a884a49e 100644 --- a/src/main/java/life/mosu/mosuserver/global/filter/AuthConstants.java +++ b/src/main/java/life/mosu/mosuserver/global/filter/AuthConstants.java @@ -18,6 +18,7 @@ public class AuthConstants { public static final String AUTH_PREFIX = API_PREFIX + "/auth"; public static final String PATH_REISSUE = AUTH_PREFIX + "/reissue"; public static final String PATH_SIGNUP = AUTH_PREFIX + "/signup"; + public static final String COOKIE_ACCESS = AUTH_PREFIX + "/check-cookie"; public static final String USER_PREFIX = API_PREFIX + "/user"; public static final String PATH_PASSWORD_CHANGE = USER_PREFIX + "/me/password"; diff --git a/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java b/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java index 6a109baf..1ff63eb9 100644 --- a/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java +++ b/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java @@ -78,6 +78,26 @@ protected void doFilterInternal( return; } + if (requestUri.startsWith(AuthConstants.COOKIE_ACCESS)) { + + final TokenCookies tokenCookies = tokenResolver.resolveTokens(request); + String accessToken = tokenCookies.getAccessToken().orElseThrow( + () -> new CustomRuntimeException(ErrorCode.COOKIE_NOT_FOUND) + ); + try { + setAuthentication(accessToken); + + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + return; + } catch (CustomRuntimeException e) { + log.warn("쿠키 토큰 검증 실패: {}", e.getMessage()); + throw e; + } catch (Exception e) { + log.error("쿠키 토큰 검증 중 예외 발생", e); + throw new CustomRuntimeException(ErrorCode.INVALID_TOKEN); + } + } + final TokenCookies tokenCookies = tokenResolver.resolveTokens(request); String accessToken = tokenCookies.getAccessToken().orElseThrow( () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_ACCESS_TOKEN) diff --git a/src/main/java/life/mosu/mosuserver/global/util/CookieBuilderUtil.java b/src/main/java/life/mosu/mosuserver/global/util/CookieBuilderUtil.java index 7f5ea9aa..3949908e 100644 --- a/src/main/java/life/mosu/mosuserver/global/util/CookieBuilderUtil.java +++ b/src/main/java/life/mosu/mosuserver/global/util/CookieBuilderUtil.java @@ -81,7 +81,7 @@ public static ResponseCookie createDevelopResponseCookie(String name, String val */ public static Cookie createDevelopCookie(String name, String value, Long maxAge) { Cookie cookie = createBaseServletCookie(name, value, maxAge); - cookie.setSecure(true); + cookie.setSecure(false); cookie.setDomain(".mosuedu.com"); return cookie; } diff --git a/src/main/java/life/mosu/mosuserver/infra/persistence/s3/S3Service.java b/src/main/java/life/mosu/mosuserver/infra/persistence/s3/S3Service.java index 823f978b..1abdec88 100644 --- a/src/main/java/life/mosu/mosuserver/infra/persistence/s3/S3Service.java +++ b/src/main/java/life/mosu/mosuserver/infra/persistence/s3/S3Service.java @@ -31,13 +31,23 @@ @RequiredArgsConstructor public class S3Service { + private static final int MAX_FILENAME_LENGTH = 150; + private static final int MAX_S3_KEY_LENGTH = 255; + private final S3Client s3Client; private final S3Presigner s3Presigner; private final S3Properties s3Properties; public FileUploadResponse uploadFile(MultipartFile file, Folder folder) { String sanitizedName = sanitizeFileName(file.getOriginalFilename()); - String s3Key = folder.getPath() + "/" + UUID.randomUUID() + "_" + sanitizedName; + String randomPrefix = UUID.randomUUID().toString(); + String s3Key = folder.getPath() + "/" + randomPrefix + "_" + sanitizedName; + + if (s3Key.length() > MAX_S3_KEY_LENGTH) { + int excess = s3Key.length() - MAX_S3_KEY_LENGTH; + sanitizedName = sanitizedName.substring(0, sanitizedName.length() - excess); + s3Key = folder.getPath() + "/" + randomPrefix + "_" + sanitizedName; + } try { s3Client.putObject( @@ -108,12 +118,23 @@ public String getPreSignedUrl(String s3Key) { } private String sanitizeFileName(String originalFilename) { - try { - return URLEncoder.encode(originalFilename, StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - } catch (Exception e) { - throw new RuntimeException("파일 이름 인코딩 실패", e); + + String encoded = URLEncoder.encode(originalFilename, StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"); + + // 파일명만 잘라내기 (확장자 유지) + String extension = ""; + int dotIndex = encoded.lastIndexOf('.'); + if (dotIndex != -1) { + extension = encoded.substring(dotIndex); + encoded = encoded.substring(0, dotIndex); + } + + if (encoded.length() > MAX_FILENAME_LENGTH) { + encoded = encoded.substring(0, MAX_FILENAME_LENGTH); } + + return encoded; } private String shortenKey(String key) { diff --git a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java index a75acc3a..e517ecb5 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java @@ -12,6 +12,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -42,15 +43,22 @@ public ResponseEntity> login( )); } + @GetMapping("/check-cookie") + public ResponseEntity checkToken() { + return ResponseEntity.ok().build(); + } + + ; + private HttpHeaders applyTokenHeader(Token token) { HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createDevelopCookieString( + headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createLocalCookieString( CookieBuilderUtil.ACCESS_TOKEN_COOKIE_NAME, token.accessToken(), token.accessTokenExpireTime() )); - headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createDevelopCookieString( + headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createLocalCookieString( CookieBuilderUtil.REFRESH_TOKEN_COOKIE_NAME, token.refreshToken(), token.refreshTokenExpireTime() diff --git a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java index 59024185..88411980 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java +++ b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java @@ -15,4 +15,7 @@ public interface AuthControllerDocs { @Operation(description = "로그인 API 지금은 쿠키와 response 둘다 반환하는데 곧 쿠키로만 작동하게 할 것 입니다. <프론트하고 변경하려고 Response 이렇게 만들었는데 나중에 같이 맞춥시다!>", summary = "사용자가 로그인합니다.") public ResponseEntity> login( @RequestBody @Valid final LoginRequest request); + + @Operation(description = "쿠키 검증용 API", summary = "쿠키가 유효한지 확인합니다.") + public ResponseEntity checkToken(); }