Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public class FilterConfig {
@Value("${device-id.cookie.max-age}")
private int cookieMaxAge;

@Value("${device-id.cookie.same-site}")
private String cookieSameSite;

@Value("${device-id.cookie.secure}")
private boolean cookieSecure;

private final DeviceIdGenerator deviceIdGenerator;
private final LikeRateLimiter likeRateLimiter;
private final ObjectMapper objectMapper;
Expand All @@ -33,7 +39,7 @@ public FilterRegistrationBean<ClientIpFilter> clientIpFilter() {

@Bean
public FilterRegistrationBean<DeviceIdCookieFilter> deviceIdCookieFilter() {
DeviceIdCookieFilter filter = new DeviceIdCookieFilter(cookieMaxAge, deviceIdGenerator);
DeviceIdCookieFilter filter = new DeviceIdCookieFilter(cookieMaxAge, cookieSameSite, cookieSecure, deviceIdGenerator);
FilterRegistrationBean<DeviceIdCookieFilter> bean = new FilterRegistrationBean<>(filter);
bean.addUrlPatterns("/api/v1/booths/*");
bean.setOrder(2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ public class DeviceIdCookieFilter implements Filter {
private static final String COOKIE_NAME = "deviceId";

private final int cookieMaxAge;
private final String cookieSameSite;
private final boolean cookieSecure;
private final DeviceIdGenerator deviceIdGenerator;

public DeviceIdCookieFilter(int cookieMaxAge, DeviceIdGenerator deviceIdGenerator) {
public DeviceIdCookieFilter(int cookieMaxAge, String cookieSameSite, boolean cookieSecure, DeviceIdGenerator deviceIdGenerator) {
this.cookieMaxAge = cookieMaxAge;
this.cookieSameSite = cookieSameSite;
this.cookieSecure = cookieSecure;
this.deviceIdGenerator = deviceIdGenerator;
}

Expand All @@ -41,11 +45,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
String deviceId = resolveDeviceId(httpRequest);
if (deviceId == null) {
deviceId = deviceIdGenerator.generate();
Cookie cookie = new Cookie(COOKIE_NAME, deviceId);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(cookieMaxAge);
httpResponse.addCookie(cookie);
String cookieHeader = buildCookieHeader(deviceId);
httpResponse.addHeader("Set-Cookie", cookieHeader);
log.debug("[DeviceId] 신규 발급 ip={} deviceId={}",
httpRequest.getAttribute(ClientIpFilter.CLIENT_IP_ATTRIBUTE), deviceId);
}
Expand All @@ -54,7 +55,20 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
chain.doFilter(request, response);
}

private String buildCookieHeader(String deviceId) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("%s=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=%s",
COOKIE_NAME, deviceId, cookieMaxAge, cookieSameSite));
if (cookieSecure) {
sb.append("; Secure");
}
return sb.toString();
}

private boolean isLikesPath(HttpServletRequest request) {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return false;
}
String uri = request.getRequestURI();
return uri.matches(".*/booths/[^/]+/likes$");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
public class LikeRateLimiter {

private static final String KEY = "like:rate:%s:%s";
private static final Duration WINDOW = Duration.ofMinutes(10);

private final StringRedisTemplate redisTemplate;
private final LikeProperties likeProperties;

public boolean isAllowed(String clientIp, String deviceId) {
String key = KEY.formatted(clientIp, deviceId);
long maxLikes = likeProperties.rateLimit().maxLikes();
Duration window = Duration.ofSeconds(likeProperties.rateLimit().ttlSeconds());

String countStr = redisTemplate.opsForValue().get(key);
long currentCount = countStr == null ? 0 : Long.parseLong(countStr);
Expand All @@ -29,7 +29,7 @@ public boolean isAllowed(String clientIp, String deviceId) {

Long newCount = redisTemplate.opsForValue().increment(key);
if (newCount == 1) {
redisTemplate.expire(key, WINDOW);
redisTemplate.expire(key, window);
}

return true;
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ spring:
device-id:
cookie:
max-age: ${DEVICE_ID_COOKIE_MAX_AGE:31536000} # 기본값 1년 (초 단위)
same-site: ${DEVICE_ID_COOKIE_SAME_SITE:Lax}
secure: ${DEVICE_ID_COOKIE_SECURE:true}

like:
rate-limit:
Expand Down
Loading