From 5638216e0067af022449334c1884dba7c2b99b90 Mon Sep 17 00:00:00 2001 From: InJun2 Date: Sun, 7 Jul 2024 22:54:52 +0900 Subject: [PATCH] =?UTF-8?q?uuid=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20js=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../urlshortener/error/dto/ErrorMessage.java | 1 + .../exception/url/NotFoundClientIdHeader.java | 11 ++++ .../ratelimit/aspect/RateLimitAspect.java | 35 ++++-------- .../ratelimit/service/RateLimitService.java | 3 +- src/main/resources/templates/main.html | 57 ++++++++++++++++++- 5 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/urlshortener/error/exception/url/NotFoundClientIdHeader.java diff --git a/src/main/java/com/urlshortener/error/dto/ErrorMessage.java b/src/main/java/com/urlshortener/error/dto/ErrorMessage.java index 9847645..266f85a 100644 --- a/src/main/java/com/urlshortener/error/dto/ErrorMessage.java +++ b/src/main/java/com/urlshortener/error/dto/ErrorMessage.java @@ -14,6 +14,7 @@ public enum ErrorMessage { NOT_FOUND_URL(HttpStatus.NOT_FOUND, "존재하지 않는 url"), RATE_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "요청 횟수 초과"), + NOT_FOUND_CLIENT_ID_HEADER(HttpStatus.BAD_REQUEST, "요청 클라이언트 ID 헤더 없음"), NOT_FINISH_DELETE_SIX_MONTHS_OLD_DATA(HttpStatus.NO_CONTENT, "6개월이 지난 데이터 삭제 실패"), diff --git a/src/main/java/com/urlshortener/error/exception/url/NotFoundClientIdHeader.java b/src/main/java/com/urlshortener/error/exception/url/NotFoundClientIdHeader.java new file mode 100644 index 0000000..d1ad523 --- /dev/null +++ b/src/main/java/com/urlshortener/error/exception/url/NotFoundClientIdHeader.java @@ -0,0 +1,11 @@ +package com.urlshortener.error.exception.url; + +import com.urlshortener.error.dto.ErrorMessage; +import com.urlshortener.error.exception.BusinessException; + +public class NotFoundClientIdHeader extends BusinessException { + + public NotFoundClientIdHeader(ErrorMessage message) { + super(message); + } +} diff --git a/src/main/java/com/urlshortener/ratelimit/aspect/RateLimitAspect.java b/src/main/java/com/urlshortener/ratelimit/aspect/RateLimitAspect.java index 52dd48a..c4615fe 100644 --- a/src/main/java/com/urlshortener/ratelimit/aspect/RateLimitAspect.java +++ b/src/main/java/com/urlshortener/ratelimit/aspect/RateLimitAspect.java @@ -1,19 +1,18 @@ package com.urlshortener.ratelimit.aspect; import com.urlshortener.error.dto.ErrorMessage; +import com.urlshortener.error.exception.url.NotFoundClientIdHeader; import com.urlshortener.error.exception.url.RateLimitExceededException; import com.urlshortener.ratelimit.annotation.RateLimit; import com.urlshortener.ratelimit.service.RateLimitService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import java.util.Arrays; -import java.util.UUID; @Aspect @Component @@ -21,7 +20,6 @@ public class RateLimitAspect { private final RateLimitService rateLimitService; private final HttpServletRequest request; - private final HttpServletResponse response; /** * UUID 사용한 요청 횟수 제한 @@ -31,44 +29,31 @@ public class RateLimitAspect { */ @Before("@annotation(rateLimit)") public void checkRateLimit(RateLimit rateLimit) { - String clientId = getClientIdFromCookie(request, response); + String clientId = getClientIdFromCookie(request); if (!rateLimitService.tryConsume(clientId, rateLimit)) { throw new RateLimitExceededException(ErrorMessage.RATE_LIMIT_EXCEEDED); } } +// web Cookie uuid (요청 제한) +// uuid -> User class (uuid를 컨트롤러로 보내서 유저 클래스를 생성하고 id를 Short 넣어라) +// User class id -> controller shortUrl -> use () /** - * 쿠키 조회 및 쿠키가 없을 시 생성 + * 쿠키 조회 * - * @param request 쿠키 조회, response 쿠키 등록 + * @param request 쿠키 조회 * @return clientCookie UUID */ - private String getClientIdFromCookie(HttpServletRequest request, HttpServletResponse response) { + private String getClientIdFromCookie(HttpServletRequest request) { if (request.getCookies() == null) { - return createClientIdFromCookie(response); + throw new NotFoundClientIdHeader(ErrorMessage.NOT_FOUND_CLIENT_ID_HEADER); } return Arrays.stream(request.getCookies()) .filter(cookie -> "client-id".equals(cookie.getName())) .map(Cookie::getValue) .findFirst() - .orElseGet(() -> createClientIdFromCookie(response)); - } - - /** - * 쿠키 생성 및 쿠키 저장 - * - * @param response 쿠키 등록 - * @return clientCookie UUID - */ - private String createClientIdFromCookie(HttpServletResponse response) { - String clientId = UUID.randomUUID().toString(); - Cookie cookie = new Cookie("client-id", clientId); - cookie.setPath("/"); - cookie.setMaxAge(60 * 60 * 24 * 183); - response.addCookie(cookie); - - return clientId; + .orElseThrow(() -> new NotFoundClientIdHeader(ErrorMessage.NOT_FOUND_CLIENT_ID_HEADER)); } } \ No newline at end of file diff --git a/src/main/java/com/urlshortener/ratelimit/service/RateLimitService.java b/src/main/java/com/urlshortener/ratelimit/service/RateLimitService.java index 50a26f1..9c6f0a8 100644 --- a/src/main/java/com/urlshortener/ratelimit/service/RateLimitService.java +++ b/src/main/java/com/urlshortener/ratelimit/service/RateLimitService.java @@ -17,7 +17,8 @@ public class RateLimitService { /** * Client UUID 조회를 통한 요청 횟수 유효성 검사 * - * @param clientId Client UUID -> 일정 기간 내의 요청 횟수를 가져오기 위한 UUID key + * @param clientId -> Client UUID : 일정 기간 내의 요청 횟수를 가져오기 위한 UUID key + * @param rateLimit -> 요청 제한 횟수, 제한 시간 (Minutes) 설정 * @return boolean -> 요청 횟수 성공 여부 */ public boolean tryConsume(String clientId, RateLimit rateLimit) { diff --git a/src/main/resources/templates/main.html b/src/main/resources/templates/main.html index d3ed585..d41da26 100644 --- a/src/main/resources/templates/main.html +++ b/src/main/resources/templates/main.html @@ -37,28 +37,79 @@ $(document).ready(function() { $('.form-signin').submit(function(event) { event.preventDefault(); - var originUrl = $('input[name="originUrl"]').val(); + const originUrl = $('input[name="originUrl"]').val(); + const clientId = getClientIdFromCookie(); $.ajax({ url: '/api/v1/short', type: 'POST', contentType: 'application/json', + headers: { + 'client-id': clientId + }, data: JSON.stringify({ "originUrl": originUrl }), success: function(response) { $('.alert').remove(); - var alertSuccess = '
' + + const alertSuccess = ''; $('.form-group').after(alertSuccess); }, error: function(response) { $('.alert').remove(); - var alertError = '
'+JSON.parse(response.responseText).message+'
'; + const alertError = '
' + JSON.parse(response.responseText).message + '
'; $('.form-group').after(alertError); } }); }); }); + + // UUID 생성 함수 + function generateUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + + return v.toString(16); + }); + } + + // 쿠키 값을 가져오는 함수 + function getCookie(name) { + let value = `; ${document.cookie}`; + let parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return parts.pop().split(';').shift(); + } + } + + // 쿠키를 설정하는 함수 + function setCookie(name, value, days) { + let expires = ""; + if (days) { + let date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + (value || "") + expires + "; path=/"; + } + + // 클라이언트 ID를 쿠키에서 가져오거나 없으면 생성하여 설정하는 함수 + function getClientIdFromCookie() { + let clientId = getCookie("client-id"); + if (!clientId) { + clientId = createClientIdFromCookie(); + } + + return clientId; + } + + // UUID를 생성하여 쿠키에 설정하는 함수 + function createClientIdFromCookie() { + const clientId = generateUUID(); + setCookie("client-id", clientId, 183); // 쿠키 유효기간 183일 설정 + + return clientId; + }