From 9ad6a91a561f380352be937c48a3845465e5a914 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Sun, 31 May 2026 21:46:27 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9E=90=EB=8F=99=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8(=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A7=80)=20-=20rememberMe=20=EC=8B=9C=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=2030=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../java/com/sb/web/auth/dto/LoginRequest.java | 3 +++ .../java/com/sb/web/auth/dto/SessionUser.java | 1 + .../com/sb/web/auth/service/AuthService.java | 16 +++++++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/sb/web/auth/dto/LoginRequest.java b/src/main/java/com/sb/web/auth/dto/LoginRequest.java index 0c11965..a9283c4 100644 --- a/src/main/java/com/sb/web/auth/dto/LoginRequest.java +++ b/src/main/java/com/sb/web/auth/dto/LoginRequest.java @@ -14,4 +14,7 @@ public class LoginRequest { @NotBlank(message = "비밀번호를 입력하세요.") private String password; + + /** 로그인 상태 유지(자동 로그인). true 면 세션을 길게(30일) 유지 */ + private boolean rememberMe; } diff --git a/src/main/java/com/sb/web/auth/dto/SessionUser.java b/src/main/java/com/sb/web/auth/dto/SessionUser.java index 7cf03b3..f96255d 100644 --- a/src/main/java/com/sb/web/auth/dto/SessionUser.java +++ b/src/main/java/com/sb/web/auth/dto/SessionUser.java @@ -23,6 +23,7 @@ public class SessionUser implements Serializable { private String name; private String role; private String provider; + private boolean rememberMe; // 자동 로그인 세션 여부 (슬라이딩 만료 TTL 결정용) public static SessionUser from(Member m) { return SessionUser.builder() diff --git a/src/main/java/com/sb/web/auth/service/AuthService.java b/src/main/java/com/sb/web/auth/service/AuthService.java index 33d528a..347a222 100644 --- a/src/main/java/com/sb/web/auth/service/AuthService.java +++ b/src/main/java/com/sb/web/auth/service/AuthService.java @@ -36,7 +36,8 @@ public class AuthService { private final RedisTemplate redisTemplate; private static final String SESSION_PREFIX = "session:"; - private static final Duration SESSION_TTL = Duration.ofMinutes(60); + private static final Duration SESSION_TTL = Duration.ofMinutes(60); // 일반 세션 + private static final Duration REMEMBER_TTL = Duration.ofDays(30); // 자동 로그인(로그인 상태 유지) @Transactional public MemberResponse signup(SignupRequest req) { @@ -68,13 +69,17 @@ public class AuthService { throw new ApiException(HttpStatus.FORBIDDEN, "사용할 수 없는 계정입니다."); } + Duration ttl = req.isRememberMe() ? REMEMBER_TTL : SESSION_TTL; + SessionUser session = SessionUser.from(member); + session.setRememberMe(req.isRememberMe()); + String token = UUID.randomUUID().toString().replace("-", ""); - redisTemplate.opsForValue().set(SESSION_PREFIX + token, SessionUser.from(member), SESSION_TTL); - log.info("[login] {} (token issued)", member.getLoginId()); + redisTemplate.opsForValue().set(SESSION_PREFIX + token, session, ttl); + log.info("[login] {} (token issued, rememberMe={})", member.getLoginId(), req.isRememberMe()); return LoginResponse.builder() .token(token) - .expiresInSeconds(SESSION_TTL.getSeconds()) + .expiresInSeconds(ttl.getSeconds()) .member(MemberResponse.from(member)) .build(); } @@ -89,7 +94,8 @@ public class AuthService { String key = SESSION_PREFIX + token; Object cached = redisTemplate.opsForValue().get(key); if (cached instanceof SessionUser user) { - redisTemplate.expire(key, SESSION_TTL); + // 슬라이딩 만료: 자동 로그인 세션이면 길게(30일), 아니면 60분으로 갱신 + redisTemplate.expire(key, user.isRememberMe() ? REMEMBER_TTL : SESSION_TTL); return user; } return null;