From d24894cab7f869f4e93bcd727b1e76f2dc8e140f Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 10:32:06 +0900 Subject: [PATCH 1/9] =?UTF-8?q?refactor(app):=20User,=20Database=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20-=20User=EC=9D=98=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EB=AA=A9=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?-=20Database=EC=9D=98=20User=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD(sequential=20key=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/app/db/Database.java | 19 ++++++++++++----- .../java/app/handler/RegisterWithGet.java | 9 ++++---- .../java/app/handler/RegisterWithPost.java | 9 ++++---- src/main/java/app/model/User.java | 21 +++++++++++-------- src/main/java/http/HttpStatus.java | 2 ++ .../resources/static/registration/index.html | 12 +++++------ 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/main/java/app/db/Database.java b/src/main/java/app/db/Database.java index 820a1050b..8a95d06ac 100644 --- a/src/main/java/app/db/Database.java +++ b/src/main/java/app/db/Database.java @@ -3,18 +3,27 @@ import app.model.User; import java.util.Collection; -import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; public class Database { - private static Map users = new HashMap<>(); + private static Map users = new ConcurrentHashMap<>(); + private static AtomicLong sequentialId = new AtomicLong(0); public static void addUser(User user) { - users.put(user.getUserId(), user); + long id = sequentialId.getAndIncrement(); + user.setUserId(id); + users.put(id, user); } - public static User findUserById(String userId) { - return users.get(userId); + public static Optional findUserById(Long userId) { + return Optional.ofNullable(users.get(userId)); + } + + public static Optional findUserByEmail(String email){ + return users.values().stream().filter(u -> u.getEmail().equals(email)).findAny(); } public static Collection findAll() { diff --git a/src/main/java/app/handler/RegisterWithGet.java b/src/main/java/app/handler/RegisterWithGet.java index 4c8ac001a..7947fb940 100644 --- a/src/main/java/app/handler/RegisterWithGet.java +++ b/src/main/java/app/handler/RegisterWithGet.java @@ -22,12 +22,11 @@ public RegisterWithGet() { @Override public HandlerResponse handle(QueryParameters params) { - String userId = params.getQueryValue("userId").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "userId required")); - String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required")); - String name = params.getQueryValue("name").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "name required")); String email = params.getQueryValue("email").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "email required")); - Database.addUser(new User(userId, password, name, email)); - log.info("Registered - userId:{}, password:{}, name:{}, email:{}", userId, password, name, email); + String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required")); + String nickname = params.getQueryValue("nickname").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "nickname required")); + Database.addUser(new User(password, nickname, email)); + log.info("Registered - password:{}, nickname:{}, email:{}", password, nickname, email); return StaticViewResponse.of("/login"); } } diff --git a/src/main/java/app/handler/RegisterWithPost.java b/src/main/java/app/handler/RegisterWithPost.java index 9b4511b6c..bff98b97c 100644 --- a/src/main/java/app/handler/RegisterWithPost.java +++ b/src/main/java/app/handler/RegisterWithPost.java @@ -21,12 +21,11 @@ public RegisterWithPost() { @Override public HandlerResponse handle(QueryParameters params) { - String userId = params.getQueryValue("userId").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "userId required")); - String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required")); - String name = params.getQueryValue("name").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "name required")); String email = params.getQueryValue("email").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "email required")); - Database.addUser(new User(userId, password, name, email)); - log.info("Registered - userId:{}, password:{}, name:{}, email:{}", userId, password, name, email); + String nickname = params.getQueryValue("nickname").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "nickname required")); + String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required")); + Database.addUser(new User(password, nickname, email)); + log.info("Registered - password:{}, nickname:{}, email:{}", password, nickname, email); return StaticViewResponse.of("/login"); } } diff --git a/src/main/java/app/model/User.java b/src/main/java/app/model/User.java index 4010a8432..91ff370ef 100644 --- a/src/main/java/app/model/User.java +++ b/src/main/java/app/model/User.java @@ -1,28 +1,31 @@ package app.model; public class User { - private String userId; + private Long userId; private String password; - private String name; + private String nickname; private String email; - public User(String userId, String password, String name, String email) { - this.userId = userId; + public User(String password, String nickname, String email) { this.password = password; - this.name = name; + this.nickname = nickname; this.email = email; } - public String getUserId() { + public Long getUserId() { return userId; } + public void setUserId(Long userId){ + this.userId = userId; + } + public String getPassword() { return password; } - public String getName() { - return name; + public String getNickname() { + return nickname; } public String getEmail() { @@ -31,6 +34,6 @@ public String getEmail() { @Override public String toString() { - return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email=" + email + "]"; + return "User [userId=" + userId + ", password=" + password + ", name=" + nickname + ", email=" + email + "]"; } } diff --git a/src/main/java/http/HttpStatus.java b/src/main/java/http/HttpStatus.java index 78836466f..9b1142798 100644 --- a/src/main/java/http/HttpStatus.java +++ b/src/main/java/http/HttpStatus.java @@ -8,6 +8,8 @@ public enum HttpStatus { ACCEPTED(202), NO_CONTENT(204), + FOUND(302), + BAD_REQUEST(400), UNAUTHORIZED(401), FORBIDDEN(403), diff --git a/src/main/resources/static/registration/index.html b/src/main/resources/static/registration/index.html index 7e109c32e..4222dd38a 100644 --- a/src/main/resources/static/registration/index.html +++ b/src/main/resources/static/registration/index.html @@ -23,14 +23,14 @@

회원가입

-
+
-

아이디

+

이메일

@@ -39,7 +39,7 @@

회원가입

class="input_textfield" name="nickname" placeholder="닉네임을 입력해주세요" - autocomplete="username" + autocomplete="nickname" />
From eab5ba21682b65f02e8a75a65142321898956408 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 12:34:04 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat(web):=20Redirect=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20-=20Redirect=20Renderer/WebResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/config/AppConfig.java | 48 ++++++++++++++----- .../java/web/renderer/RedirectRenderer.java | 26 ++++++++++ .../java/web/response/RedirectResponse.java | 18 +++++++ 3 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 src/main/java/web/renderer/RedirectRenderer.java create mode 100644 src/main/java/web/response/RedirectResponse.java diff --git a/src/main/java/config/AppConfig.java b/src/main/java/config/AppConfig.java index 540260975..daf081b3e 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -1,5 +1,7 @@ package config; +import app.handler.LoginWithPost; +import app.handler.LogoutWithPost; import app.handler.RegisterWithGet; import app.handler.RegisterWithPost; import exception.ExceptionHandlerMapping; @@ -24,6 +26,7 @@ import web.handler.StaticContentHandler; import web.handler.WebHandler; import web.renderer.HttpResponseRenderer; +import web.renderer.RedirectRenderer; import web.renderer.StaticViewRenderer; import java.util.List; @@ -71,7 +74,7 @@ public Dispatcher dispatcher() { () -> new Dispatcher( webHandlerList(), handlerAdapterList(), - webHandlerResponseHandlerList() + httpResponseRendererList() ) ); } @@ -82,11 +85,20 @@ public List webHandlerList() { () -> List.of( staticContentHandler(), registerWithGet(), - registerWithPost() + registerWithPost(), + loginWithPost(), + logoutWithPost() ) ); } + public StaticContentHandler staticContentHandler() { + return getOrCreate( + "staticContentHandler", + StaticContentHandler::new + ); + } + public RegisterWithGet registerWithGet() { return getOrCreate( "registerWithGet", @@ -101,29 +113,39 @@ public RegisterWithPost registerWithPost() { ); } - public List webHandlerResponseHandlerList() { + public LoginWithPost loginWithPost() { + return getOrCreate("loginWithPost", + () -> new LoginWithPost(sessionStorage())); + } + + public LogoutWithPost logoutWithPost() { + return getOrCreate("logoutWithPost", + () -> new LogoutWithPost(sessionStorage())); + } + + // ===== Renderer ===== + public List httpResponseRendererList() { return getOrCreate( - "webHandlerResponseHandlerList", + "httpResponseRendererList", () -> List.of( - staticViewResponseHandler() + staticViewRenderer(), + redirectRenderer() ) ); } - public StaticContentHandler staticContentHandler() { + public StaticViewRenderer staticViewRenderer() { return getOrCreate( - "staticContentHandler", - StaticContentHandler::new + "staticViewRenderer", + StaticViewRenderer::new ); } - public StaticViewRenderer staticViewResponseHandler() { - return getOrCreate( - "staticViewResponseHandler", - StaticViewRenderer::new - ); + public RedirectRenderer redirectRenderer() { + return getOrCreate("redirectRenderer", RedirectRenderer::new); } + // ===== Adapter ===== public List handlerAdapterList() { return getOrCreate( diff --git a/src/main/java/web/renderer/RedirectRenderer.java b/src/main/java/web/renderer/RedirectRenderer.java new file mode 100644 index 000000000..c8daafb50 --- /dev/null +++ b/src/main/java/web/renderer/RedirectRenderer.java @@ -0,0 +1,26 @@ +package web.renderer; + +import http.response.HttpResponse; +import web.response.HandlerResponse; +import web.response.RedirectResponse; + +public class RedirectRenderer implements HttpResponseRenderer { + + @Override + public boolean supports(HandlerResponse response) { + return response instanceof RedirectResponse; + } + + @Override + public HttpResponse handle(HttpResponse httpResponse, HandlerResponse handlerResponse) { + RedirectResponse rr = (RedirectResponse) handlerResponse; + + httpResponse.setStatus(rr.getStatus()); + httpResponse.setHeader("Location", rr.getLocation()); + httpResponse.setHeader("Content-Length", "0"); + + rr.getCookies().forEach(c -> httpResponse.addHeader("Set-Cookie", c)); + return httpResponse; + } +} + diff --git a/src/main/java/web/response/RedirectResponse.java b/src/main/java/web/response/RedirectResponse.java new file mode 100644 index 000000000..04e6b908a --- /dev/null +++ b/src/main/java/web/response/RedirectResponse.java @@ -0,0 +1,18 @@ +package web.response; + +import http.HttpStatus; + +public class RedirectResponse extends HandlerResponse { + private final String location; + + private RedirectResponse(String location) { + super(HttpStatus.FOUND); + this.location = location; + } + + public static RedirectResponse to(String location) { + return new RedirectResponse(location); + } + + public String getLocation() { return location; } +} From 9c7e3d08b05fc5220cea97e75648fc546ca31408 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 16:36:53 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat(session):=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20-=20=EC=84=B8=EC=85=98=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=A0=95=EC=9D=98=20-=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/app/handler/RegisterWithGet.java | 3 +- .../java/app/handler/RegisterWithPost.java | 3 +- src/main/java/app/model/User.java | 8 +++- src/main/java/config/VariableConfig.java | 3 ++ .../web/filter/authentication/UserRole.java | 6 +++ src/main/java/web/session/SessionEntity.java | 26 +++++++++++++ src/main/java/web/session/SessionStorage.java | 39 +++++++++++++++++++ 7 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/main/java/web/filter/authentication/UserRole.java create mode 100644 src/main/java/web/session/SessionEntity.java create mode 100644 src/main/java/web/session/SessionStorage.java diff --git a/src/main/java/app/handler/RegisterWithGet.java b/src/main/java/app/handler/RegisterWithGet.java index 7947fb940..2d9ab46f7 100644 --- a/src/main/java/app/handler/RegisterWithGet.java +++ b/src/main/java/app/handler/RegisterWithGet.java @@ -8,6 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.dispatch.argument.QueryParameters; +import web.filter.authentication.UserRole; import web.handler.SingleArgHandler; import web.response.HandlerResponse; import web.response.StaticViewResponse; @@ -25,7 +26,7 @@ public HandlerResponse handle(QueryParameters params) { String email = params.getQueryValue("email").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "email required")); String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required")); String nickname = params.getQueryValue("nickname").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "nickname required")); - Database.addUser(new User(password, nickname, email)); + Database.addUser(new User(password, nickname, email, UserRole.MEMBER.toString())); log.info("Registered - password:{}, nickname:{}, email:{}", password, nickname, email); return StaticViewResponse.of("/login"); } diff --git a/src/main/java/app/handler/RegisterWithPost.java b/src/main/java/app/handler/RegisterWithPost.java index bff98b97c..f1844c3a1 100644 --- a/src/main/java/app/handler/RegisterWithPost.java +++ b/src/main/java/app/handler/RegisterWithPost.java @@ -8,6 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.dispatch.argument.QueryParameters; +import web.filter.authentication.UserRole; import web.handler.SingleArgHandler; import web.response.HandlerResponse; import web.response.StaticViewResponse; @@ -24,7 +25,7 @@ public HandlerResponse handle(QueryParameters params) { String email = params.getQueryValue("email").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "email required")); String nickname = params.getQueryValue("nickname").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "nickname required")); String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required")); - Database.addUser(new User(password, nickname, email)); + Database.addUser(new User(password, nickname, email, UserRole.MEMBER.toString())); log.info("Registered - password:{}, nickname:{}, email:{}", password, nickname, email); return StaticViewResponse.of("/login"); } diff --git a/src/main/java/app/model/User.java b/src/main/java/app/model/User.java index 91ff370ef..550178337 100644 --- a/src/main/java/app/model/User.java +++ b/src/main/java/app/model/User.java @@ -5,11 +5,13 @@ public class User { private String password; private String nickname; private String email; + private String userRole; - public User(String password, String nickname, String email) { + public User(String password, String nickname, String email, String userRole) { this.password = password; this.nickname = nickname; this.email = email; + this.userRole = userRole; } public Long getUserId() { @@ -32,6 +34,10 @@ public String getEmail() { return email; } + public String getUserRole() { + return userRole; + } + @Override public String toString() { return "User [userId=" + userId + ", password=" + password + ", name=" + nickname + ", email=" + email + "]"; diff --git a/src/main/java/config/VariableConfig.java b/src/main/java/config/VariableConfig.java index 239bbc662..d458a608b 100644 --- a/src/main/java/config/VariableConfig.java +++ b/src/main/java/config/VariableConfig.java @@ -6,4 +6,7 @@ public class VariableConfig { public static final List STATIC_RESOURCE_ROOTS = List.of( "./src/main/resources", "./src/main/resources/static"); + + public static final long IDLE_MS = 30*60*100; + public static final long ABSOLUTE_MS = 180*60*100; } diff --git a/src/main/java/web/filter/authentication/UserRole.java b/src/main/java/web/filter/authentication/UserRole.java new file mode 100644 index 000000000..db76e679c --- /dev/null +++ b/src/main/java/web/filter/authentication/UserRole.java @@ -0,0 +1,6 @@ +package web.filter.authentication; + +public enum UserRole { + UNANIMOUS, + MEMBER +} diff --git a/src/main/java/web/session/SessionEntity.java b/src/main/java/web/session/SessionEntity.java new file mode 100644 index 000000000..1c27ed117 --- /dev/null +++ b/src/main/java/web/session/SessionEntity.java @@ -0,0 +1,26 @@ +package web.session; + +public class SessionEntity { + private final String id; // 세션 아이디(UUID) + private final long userId; // DB 키 + private final String userRole; + private final long createdAt; + private volatile long lastAccessAt; + + public SessionEntity(String id, long userId, String userRole, long now) { + this.id = id; + this.userId = userId; + this.userRole = userRole; + this.createdAt = now; + this.lastAccessAt = now; + } + + public void touch(long now) { this.lastAccessAt = now; } + + public String getId() { return id; } + public long getUserId() { return userId; } + public String getUserRole() { return userRole; } + public long getCreatedAt() { return createdAt; } + + public long getLastAccessAt() { return lastAccessAt; } +} diff --git a/src/main/java/web/session/SessionStorage.java b/src/main/java/web/session/SessionStorage.java new file mode 100644 index 000000000..4dc663ee3 --- /dev/null +++ b/src/main/java/web/session/SessionStorage.java @@ -0,0 +1,39 @@ +package web.session; + +import config.VariableConfig; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionStorage { + private final Map store = new ConcurrentHashMap<>(); + private final long idleMs = VariableConfig.IDLE_MS; + private final long absoluteMs = VariableConfig.ABSOLUTE_MS; + + public SessionEntity create(long userId, String userRole) { + long now = System.currentTimeMillis(); + String sid = UUID.randomUUID().toString(); + SessionEntity s = new SessionEntity(sid, userId, userRole, now); + store.put(sid, s); + return s; + } + + public SessionEntity getValid(String sid) { + if (sid == null || sid.isBlank()) return null; + SessionEntity s = store.get(sid); + if (s == null) return null; + + long now = System.currentTimeMillis(); + if (now - s.getCreatedAt() > absoluteMs || now - s.getLastAccessAt() > idleMs) { + store.remove(sid); + return null; + } + s.touch(now); + return s; + } + + public void invalidate(String sid) { + if (sid != null) store.remove(sid); + } +} From ee74fbe2ac3f2b82ba6cb063c66bd53b45c95c89 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 17:06:47 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat(web):=20Authentication=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EB=8B=B4=EB=8A=94=20AuthenticateInfo=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=B2=B4=20=EA=B0=9C=EB=B0=9C=20-=20Authenti?= =?UTF-8?q?cationInfo=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20-=20=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=EC=9A=A9=20=EA=B5=AC=ED=98=84=EC=B2=B4=20?= =?UTF-8?q?UnanimousAuthentication=20=EA=B0=9C=EB=B0=9C=20-=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=9C=A0=EC=A0=80=EC=9A=A9=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=20UnanimousAuthentication=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20-=20HttpRequest=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=9D=98=20=EB=A9=A4=EB=B2=84=EC=97=90=20AuthenticationInfo=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/http/request/HttpRequest.java | 10 +++++++ .../authentication/AuthenticationInfo.java | 8 ++++++ .../UnanimousAuthentication.java | 21 +++++++++++++++ .../authentication/UserAuthentication.java | 27 +++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 src/main/java/web/filter/authentication/AuthenticationInfo.java create mode 100644 src/main/java/web/filter/authentication/UnanimousAuthentication.java create mode 100644 src/main/java/web/filter/authentication/UserAuthentication.java diff --git a/src/main/java/http/request/HttpRequest.java b/src/main/java/http/request/HttpRequest.java index cf0c025bf..c695494f9 100644 --- a/src/main/java/http/request/HttpRequest.java +++ b/src/main/java/http/request/HttpRequest.java @@ -4,6 +4,7 @@ import exception.ErrorException; import exception.ServiceException; import http.HttpMethod; +import web.filter.authentication.AuthenticationInfo; import java.net.InetAddress; import java.net.URI; @@ -18,6 +19,7 @@ public class HttpRequest { private String contentType; private byte[] body; private UUID rid; + private AuthenticationInfo authenticationInfo; private InetAddress requestAddress; @@ -110,4 +112,12 @@ public String getOrGenerateRid(){ public UUID getRid() { return rid; } + + public AuthenticationInfo getAuthenticationInfo() { + return authenticationInfo; + } + + public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) { + this.authenticationInfo = authenticationInfo; + } } diff --git a/src/main/java/web/filter/authentication/AuthenticationInfo.java b/src/main/java/web/filter/authentication/AuthenticationInfo.java new file mode 100644 index 000000000..5db196c3d --- /dev/null +++ b/src/main/java/web/filter/authentication/AuthenticationInfo.java @@ -0,0 +1,8 @@ +package web.filter.authentication; + +import java.util.Optional; + +public interface AuthenticationInfo { + Optional getUserId(); + UserRole getRole(); +} diff --git a/src/main/java/web/filter/authentication/UnanimousAuthentication.java b/src/main/java/web/filter/authentication/UnanimousAuthentication.java new file mode 100644 index 000000000..44f15a1fa --- /dev/null +++ b/src/main/java/web/filter/authentication/UnanimousAuthentication.java @@ -0,0 +1,21 @@ +package web.filter.authentication; + +import java.util.Optional; + +public class UnanimousAuthentication implements AuthenticationInfo { + private final UserRole USER_ROLE = UserRole.UNANIMOUS; + + public static UnanimousAuthentication of(){ + return new UnanimousAuthentication(); + } + + @Override + public Optional getUserId() { + return Optional.empty(); + } + + @Override + public UserRole getRole() { + return this.USER_ROLE; + } +} diff --git a/src/main/java/web/filter/authentication/UserAuthentication.java b/src/main/java/web/filter/authentication/UserAuthentication.java new file mode 100644 index 000000000..efe82fd51 --- /dev/null +++ b/src/main/java/web/filter/authentication/UserAuthentication.java @@ -0,0 +1,27 @@ +package web.filter.authentication; + +import java.util.Optional; + +public class UserAuthentication implements AuthenticationInfo { + private final Long userId; + private final UserRole userRole; + + private UserAuthentication(Long userId, UserRole userRole) { + this.userId = userId; + this.userRole = userRole; + } + + public static UserAuthentication of(Long userId, UserRole userRole){ + return new UserAuthentication(userId, userRole); + } + + @Override + public Optional getUserId() { + return Optional.of(userId); + } + + @Override + public UserRole getRole() { + return userRole; + } +} From 9f8f8d6f077461b94f1bc7705705b07a32db9d87 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 17:10:22 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat(web-filter):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=95=84=ED=84=B0=20=EA=B0=9C=EB=B0=9C=20-=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EC=A0=95=EB=B3=B4=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EA=B0=9C=EB=B0=9C=20-=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=EB=B3=84=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/config/AppConfig.java | 25 +++++++++++-- src/main/java/config/FilterConfig.java | 17 ++++++--- src/main/java/config/SecurityConfig.java | 1 + src/main/java/web/filter/AccessLogFilter.java | 22 +++++++++--- .../java/web/filter/AuthenticationFilter.java | 36 +++++++++++++++++++ .../web/filter/MemberAuthorizationFilter.java | 20 +++++++++++ .../filter/UnanimousAuthorizationFilter.java | 20 +++++++++++ 7 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 src/main/java/web/filter/AuthenticationFilter.java create mode 100644 src/main/java/web/filter/MemberAuthorizationFilter.java create mode 100644 src/main/java/web/filter/UnanimousAuthorizationFilter.java diff --git a/src/main/java/config/AppConfig.java b/src/main/java/config/AppConfig.java index daf081b3e..df5d2d614 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -20,14 +20,13 @@ import web.dispatch.argument.ArgumentResolver; import web.dispatch.argument.resolver.HttpRequestResolver; import web.dispatch.argument.resolver.QueryParamsResolver; -import web.filter.AccessLogFilter; -import web.filter.FilterChainContainer; -import web.filter.RestrictedFilter; +import web.filter.*; import web.handler.StaticContentHandler; import web.handler.WebHandler; import web.renderer.HttpResponseRenderer; import web.renderer.RedirectRenderer; import web.renderer.StaticViewRenderer; +import web.session.SessionStorage; import java.util.List; @@ -250,4 +249,24 @@ public AccessLogFilter accessLogFilter(){ public RestrictedFilter restrictedFilter(){ return getOrCreate("restrictedFilter", RestrictedFilter::new); } + + public AuthenticationFilter authenticationFilter() { + return getOrCreate("authenticationFilter", + () -> new AuthenticationFilter(sessionStorage())); + } + + public MemberAuthorizationFilter memberAuthorizationFilter(){ + return getOrCreate("memberAuthorizationFilter", + MemberAuthorizationFilter::new); + } + + public UnanimousAuthorizationFilter unanimousAuthorizationFilter(){ + return getOrCreate("unanimousAuthorizationFilter", + UnanimousAuthorizationFilter::new); + } + + public SessionStorage sessionStorage() { + return getOrCreate("sessionStorage", + SessionStorage::new); + } } diff --git a/src/main/java/config/FilterConfig.java b/src/main/java/config/FilterConfig.java index 4f7005056..f005431c6 100644 --- a/src/main/java/config/FilterConfig.java +++ b/src/main/java/config/FilterConfig.java @@ -21,7 +21,8 @@ private void setFilterChains(){ .addFilterList(FilterType.ALL, getFilterListByAuthorityType(FilterType.ALL)) .addFilterList(FilterType.PUBLIC, getFilterListByAuthorityType(FilterType.PUBLIC)) .addFilterList(FilterType.AUTHENTICATED, getFilterListByAuthorityType(FilterType.AUTHENTICATED)) - .addFilterList(FilterType.RESTRICT, getFilterListByAuthorityType(FilterType.RESTRICT)); + .addFilterList(FilterType.RESTRICT, getFilterListByAuthorityType(FilterType.RESTRICT)) + .addFilterList(FilterType.LOG_IN, getFilterListByAuthorityType(FilterType.LOG_IN)); } private List commonFrontFilter(){ @@ -47,10 +48,16 @@ private List getFilterListByAuthorityType(FilterType type) { private List authorizedFilterList(FilterType type) { return switch (type) { case ALL -> List.of(); - case PUBLIC -> List.of(); - case AUTHENTICATED -> List.of(); - case RESTRICT -> List.of(appConfig.restrictedFilter()); - case LOG_IN -> List.of(); + case PUBLIC -> List.of( + appConfig.authenticationFilter()); + case AUTHENTICATED -> List.of( + appConfig.authenticationFilter(), + appConfig.memberAuthorizationFilter()); + case RESTRICT -> List.of( + appConfig.restrictedFilter()); + case LOG_IN -> List.of( + appConfig.authenticationFilter(), + appConfig.unanimousAuthorizationFilter()); }; } } diff --git a/src/main/java/config/SecurityConfig.java b/src/main/java/config/SecurityConfig.java index 514218be2..eb45010d2 100644 --- a/src/main/java/config/SecurityConfig.java +++ b/src/main/java/config/SecurityConfig.java @@ -15,6 +15,7 @@ public void config(){ public void setPaths(){ appConfig.filterChainContainer() .addPath(FilterType.AUTHENTICATED, "/mypage/**") + .addPath(FilterType.LOG_IN, "/user/login") .addPath(FilterType.ALL, "/user/**") .addPath(FilterType.PUBLIC, "/**"); } diff --git a/src/main/java/web/filter/AccessLogFilter.java b/src/main/java/web/filter/AccessLogFilter.java index 2cd4e2922..c050d7706 100644 --- a/src/main/java/web/filter/AccessLogFilter.java +++ b/src/main/java/web/filter/AccessLogFilter.java @@ -4,6 +4,7 @@ import http.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import web.filter.authentication.UserRole; public class AccessLogFilter implements ServletFilter { @@ -12,10 +13,21 @@ public class AccessLogFilter implements ServletFilter { @Override public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { chain.doFilter(); - log.info("rid-{}: {} {} from {}", - request.getOrGenerateRid(), - request.getMethod(), - request.getPath(), - request.getRequestAddress()); + if(request.getAuthenticationInfo()!=null && + request.getAuthenticationInfo().getRole().equals(UserRole.MEMBER)) { + log.info("userId-{}|rid-{}: {} {} from {}", + request.getAuthenticationInfo().getUserId().orElse(-1L), + request.getOrGenerateRid(), + request.getMethod(), + request.getPath(), + request.getRequestAddress()); + } + else { + log.info("rid-{}: {} {} from {}", + request.getOrGenerateRid(), + request.getMethod(), + request.getPath(), + request.getRequestAddress()); + } } } diff --git a/src/main/java/web/filter/AuthenticationFilter.java b/src/main/java/web/filter/AuthenticationFilter.java new file mode 100644 index 000000000..d5310a7f9 --- /dev/null +++ b/src/main/java/web/filter/AuthenticationFilter.java @@ -0,0 +1,36 @@ +package web.filter; + +import http.HttpStatus; +import http.request.HttpRequest; +import http.response.HttpResponse; +import web.filter.authentication.UnanimousAuthentication; +import web.filter.authentication.UserAuthentication; +import web.filter.authentication.UserRole; +import web.session.SessionEntity; +import web.session.SessionStorage; + +public class AuthenticationFilter implements ServletFilter { + private final SessionStorage sessionManager; + + public AuthenticationFilter(SessionStorage sessionManager) { + this.sessionManager = sessionManager; + } + + @Override + public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + String sid = request.getCookieValue("SID").orElse(null); + SessionEntity session = sessionManager.getValid(sid); + + if (session == null) { + request.setAuthenticationInfo( + UnanimousAuthentication.of()); + } else{ + request.setAuthenticationInfo( + UserAuthentication.of( + session.getUserId(), + UserRole.valueOf(session.getUserRole()))); + } + + chain.doFilter(); + } +} diff --git a/src/main/java/web/filter/MemberAuthorizationFilter.java b/src/main/java/web/filter/MemberAuthorizationFilter.java new file mode 100644 index 000000000..8f689c5cc --- /dev/null +++ b/src/main/java/web/filter/MemberAuthorizationFilter.java @@ -0,0 +1,20 @@ +package web.filter; + +import http.HttpStatus; +import http.request.HttpRequest; +import http.response.HttpResponse; +import web.filter.authentication.UserRole; + +public class MemberAuthorizationFilter implements ServletFilter { + + @Override + public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + if(request.getAuthenticationInfo().getRole().equals(UserRole.MEMBER)){ + chain.doFilter(); + } else { + response.setStatus(HttpStatus.FOUND); + response.setHeader("Location", "/login"); + response.setHeader("Content-Length", "0"); + } + } +} diff --git a/src/main/java/web/filter/UnanimousAuthorizationFilter.java b/src/main/java/web/filter/UnanimousAuthorizationFilter.java new file mode 100644 index 000000000..b186a26e0 --- /dev/null +++ b/src/main/java/web/filter/UnanimousAuthorizationFilter.java @@ -0,0 +1,20 @@ +package web.filter; + +import http.HttpStatus; +import http.request.HttpRequest; +import http.response.HttpResponse; +import web.filter.authentication.UserRole; + +public class UnanimousAuthorizationFilter implements ServletFilter { + private final UserRole USER_ROLE = UserRole.UNANIMOUS; + @Override + public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + if(request.getAuthenticationInfo().getRole().equals(USER_ROLE)) { + chain.doFilter(); + } else { + response.setStatus(HttpStatus.FOUND); + response.setHeader("Location", "/"); + response.setHeader("Content-Length", "0"); + } + } +} From 022bba4c6d6039167e2f8371baf1d7467928e1cc Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 17:11:10 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat(app):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=B0=8F=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20-=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EA=B0=9C=EB=B0=9C=20-=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B7=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/app/handler/LoginWithPost.java | 54 ++++++++++++++++++++ src/main/java/exception/ErrorCode.java | 2 + src/main/resources/static/login/index.html | 12 +++-- 3 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 src/main/java/app/handler/LoginWithPost.java diff --git a/src/main/java/app/handler/LoginWithPost.java b/src/main/java/app/handler/LoginWithPost.java new file mode 100644 index 000000000..feb310ef5 --- /dev/null +++ b/src/main/java/app/handler/LoginWithPost.java @@ -0,0 +1,54 @@ +package app.handler; + +import app.db.Database; +import app.model.User; +import config.VariableConfig; +import exception.ErrorCode; +import exception.ServiceException; +import http.HttpMethod; +import http.response.CookieBuilder; +import web.dispatch.argument.QueryParameters; +import web.handler.SingleArgHandler; +import web.response.HandlerResponse; +import web.response.RedirectResponse; +import web.session.SessionEntity; +import web.session.SessionStorage; + +public class LoginWithPost extends SingleArgHandler { + private final SessionStorage sessionManager; + + public LoginWithPost(SessionStorage sessionManager) { + super(HttpMethod.POST, "/user/login"); + this.sessionManager = sessionManager; + } + + @Override + public HandlerResponse handle(QueryParameters params) { + String email = params.getQueryValue("email") + .orElseThrow(() -> new ServiceException(ErrorCode.LOGIN_FAILED, "email required")); + + String password = params.getQueryValue("password") + .orElseThrow(() -> new ServiceException(ErrorCode.LOGIN_FAILED, "password required")); + + User user = Database.findUserByEmail(email) + .orElseThrow(() -> new ServiceException(ErrorCode.LOGIN_FAILED)); + + if (!user.getPassword().equals(password)) { + throw new ServiceException(ErrorCode.LOGIN_FAILED); + } + + SessionEntity session = sessionManager.create( + user.getUserId(), + user.getUserRole()); + + RedirectResponse res = RedirectResponse.to("/"); + res.setCookie( + CookieBuilder.of("SID", session.getId()) + .path("/") + .httpOnly() + .sameSite(CookieBuilder.SameSite.LAX) + .maxAge(VariableConfig.ABSOLUTE_MS) + ); + return res; + } +} diff --git a/src/main/java/exception/ErrorCode.java b/src/main/java/exception/ErrorCode.java index 53c7f9282..2a541fdc9 100644 --- a/src/main/java/exception/ErrorCode.java +++ b/src/main/java/exception/ErrorCode.java @@ -7,6 +7,8 @@ public enum ErrorCode { /* Service Exception */ MISSING_REGISTER_TOKEN( HttpStatus.BAD_REQUEST, "400_MISSING_REGISTER_TOKEN", "회원가입에 필요한 토큰이 누락되었습니다."), + LOGIN_FAILED( + HttpStatus.BAD_REQUEST, "400_LOGIN_FAILED", "아이디 혹은 비밀번호가 잘못되었습니다."), /* Internal Error */ INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500_INTERNAL", "서버 내부 오류가 발생했습니다."), diff --git a/src/main/resources/static/login/index.html b/src/main/resources/static/login/index.html index dafa96518..7c87ea590 100644 --- a/src/main/resources/static/login/index.html +++ b/src/main/resources/static/login/index.html @@ -23,19 +23,21 @@

로그인

- +
-

아이디

+

이메일

비밀번호

로그인 id="login-btn" class="btn btn_contained btn_size_m" style="margin-top: 24px" - type="button" + type="submit" > 로그인 From 2a9c8f8dcac38dbd1ccb595329931803f4b23c12 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 17:58:33 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix(test):=20User=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=9D=98=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java b/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java index 9cd61ee98..56737345e 100644 --- a/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java +++ b/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java @@ -18,7 +18,7 @@ class SingleArgHandlerAdapterTest { @Test @DisplayName("SingleArgumentHandlerAdapter 성공 테스트") void test(){ - HttpRequest request = HttpRequest.from("GET /user/create?userId=javajigi&password=password&name=%EB%B0%95%EC%9E%AC%EC%84%B1&email=javajigi%40slipp.net HTTP/1.1"); + HttpRequest request = HttpRequest.from("GET /user/create?&password=password&nickname=%EB%B0%95%EC%9E%AC%EC%84%B1&email=javajigi%40slipp.net HTTP/1.1"); WebHandler handler = new RegisterWithGet(); assertThat(adapter.support(handler)).isTrue(); From 1902a3eeb501d64f4d0cf7743a99aa0dadb39605 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 18:01:00 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix(web-filter):=20NPE=20=EB=B0=A9=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/web/filter/MemberAuthorizationFilter.java | 1 + src/main/java/web/filter/UnanimousAuthorizationFilter.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/web/filter/MemberAuthorizationFilter.java b/src/main/java/web/filter/MemberAuthorizationFilter.java index 8f689c5cc..2fab21dcb 100644 --- a/src/main/java/web/filter/MemberAuthorizationFilter.java +++ b/src/main/java/web/filter/MemberAuthorizationFilter.java @@ -9,6 +9,7 @@ public class MemberAuthorizationFilter implements ServletFilter { @Override public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + if(request.getAuthenticationInfo()==null) return; if(request.getAuthenticationInfo().getRole().equals(UserRole.MEMBER)){ chain.doFilter(); } else { diff --git a/src/main/java/web/filter/UnanimousAuthorizationFilter.java b/src/main/java/web/filter/UnanimousAuthorizationFilter.java index b186a26e0..3f91b6a81 100644 --- a/src/main/java/web/filter/UnanimousAuthorizationFilter.java +++ b/src/main/java/web/filter/UnanimousAuthorizationFilter.java @@ -9,6 +9,7 @@ public class UnanimousAuthorizationFilter implements ServletFilter { private final UserRole USER_ROLE = UserRole.UNANIMOUS; @Override public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + if(request.getAuthenticationInfo()==null) return; if(request.getAuthenticationInfo().getRole().equals(USER_ROLE)) { chain.doFilter(); } else { From 6e064fcf5956a0475014b88cca933f59787ba0bc Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Mon, 12 Jan 2026 22:29:01 +0900 Subject: [PATCH 9/9] =?UTF-8?q?fix(ci):=20LogoutHandler=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0=20-=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20=EC=BB=A4=EB=B0=8B=EC=97=90=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=ED=95=B8=EB=93=A4=EB=9F=AC=EC=9D=98=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=EC=9D=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/config/AppConfig.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/config/AppConfig.java b/src/main/java/config/AppConfig.java index df5d2d614..32545fcea 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -1,7 +1,6 @@ package config; import app.handler.LoginWithPost; -import app.handler.LogoutWithPost; import app.handler.RegisterWithGet; import app.handler.RegisterWithPost; import exception.ExceptionHandlerMapping; @@ -85,8 +84,7 @@ public List webHandlerList() { staticContentHandler(), registerWithGet(), registerWithPost(), - loginWithPost(), - logoutWithPost() + loginWithPost() ) ); } @@ -117,11 +115,6 @@ public LoginWithPost loginWithPost() { () -> new LoginWithPost(sessionStorage())); } - public LogoutWithPost logoutWithPost() { - return getOrCreate("logoutWithPost", - () -> new LogoutWithPost(sessionStorage())); - } - // ===== Renderer ===== public List httpResponseRendererList() { return getOrCreate(