feat: 회원가입 제한·봇차단(허니팟/레이트리밋)·카드 자동인식 보정
- 관리자 회원가입 허용 토글(app_setting), 공개 GET /auth/signup-enabled - 회원가입 봇차단: 허니팟(website) + IP 레이트리밋(Redis, 1h 5회) - 카드 알림: 현금 오선택 보정(카드 양방향 매칭+단일카드 자동), 광고 푸시 차단(승인신호 없는 광고성 표현 무시) - @MapperScan 에 admin.mapper 추가 - account.sql: 매 기동 wallet MODIFY 제거(라이브 락 위험) — CREATE 정의에 255 반영됨 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fb94df8112
commit
c05be0880c
@@ -34,13 +34,28 @@ public class AuthService {
|
||||
private final MemberMapper memberMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final com.sb.web.admin.service.AppSettingService appSettingService;
|
||||
|
||||
private static final String SESSION_PREFIX = "session:";
|
||||
private static final Duration SESSION_TTL = Duration.ofMinutes(60); // 일반 세션
|
||||
private static final Duration REMEMBER_TTL = Duration.ofDays(30); // 자동 로그인(로그인 상태 유지)
|
||||
|
||||
// 회원가입 봇 방지 — IP당 가입 시도 제한(슬라이딩 윈도우)
|
||||
private static final int SIGNUP_LIMIT = 5;
|
||||
private static final Duration SIGNUP_WINDOW = Duration.ofHours(1);
|
||||
|
||||
@Transactional
|
||||
public MemberResponse signup(SignupRequest req) {
|
||||
public MemberResponse signup(SignupRequest req, String clientIp) {
|
||||
if (!appSettingService.isSignupEnabled()) {
|
||||
throw new ApiException(HttpStatus.FORBIDDEN, "현재 회원가입이 제한되어 있습니다.");
|
||||
}
|
||||
// 1) 허니팟: 숨김 필드에 값이 차 있으면 봇 → 조용히 차단
|
||||
if (req.getWebsite() != null && !req.getWebsite().isBlank()) {
|
||||
log.warn("[signup] honeypot 차단 ip={}", clientIp);
|
||||
throw new ApiException(HttpStatus.BAD_REQUEST, "잘못된 요청입니다.");
|
||||
}
|
||||
// 2) IP 레이트리밋
|
||||
rateLimitSignup(clientIp);
|
||||
if (memberMapper.countByLoginId(req.getLoginId()) > 0) {
|
||||
throw new ApiException(HttpStatus.CONFLICT, "이미 사용 중인 아이디입니다.");
|
||||
}
|
||||
@@ -58,6 +73,23 @@ public class AuthService {
|
||||
return MemberResponse.from(member);
|
||||
}
|
||||
|
||||
/** IP당 가입 시도 횟수 제한 (Redis 슬라이딩 윈도우). 초과 시 429 */
|
||||
private void rateLimitSignup(String ip) {
|
||||
if (ip == null || ip.isBlank()) {
|
||||
return;
|
||||
}
|
||||
String key = "signup:rl:" + ip;
|
||||
Long count = redisTemplate.opsForValue().increment(key);
|
||||
if (count != null && count == 1L) {
|
||||
redisTemplate.expire(key, SIGNUP_WINDOW);
|
||||
}
|
||||
if (count != null && count > SIGNUP_LIMIT) {
|
||||
log.warn("[signup] 레이트리밋 차단 ip={} count={}", ip, count);
|
||||
throw new ApiException(HttpStatus.TOO_MANY_REQUESTS,
|
||||
"회원가입 시도가 너무 많습니다. 잠시 후 다시 시도해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
public LoginResponse login(LoginRequest req) {
|
||||
Member member = memberMapper.findByLoginId(req.getLoginId());
|
||||
if (member == null
|
||||
|
||||
Reference in New Issue
Block a user