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/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/app/handler/RegisterWithGet.java b/src/main/java/app/handler/RegisterWithGet.java index 4c8ac001a..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; @@ -22,12 +23,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, 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 9b4511b6c..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; @@ -21,12 +22,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, 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 4010a8432..550178337 100644 --- a/src/main/java/app/model/User.java +++ b/src/main/java/app/model/User.java @@ -1,36 +1,45 @@ package app.model; public class User { - private String userId; + private Long userId; private String password; - private String name; + private String nickname; private String email; + private String userRole; - public User(String userId, String password, String name, String email) { - this.userId = userId; + public User(String password, String nickname, String email, String userRole) { this.password = password; - this.name = name; + this.nickname = nickname; this.email = email; + this.userRole = userRole; } - 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() { return email; } + public String getUserRole() { + return userRole; + } + @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/config/AppConfig.java b/src/main/java/config/AppConfig.java index 540260975..32545fcea 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -1,5 +1,6 @@ package config; +import app.handler.LoginWithPost; import app.handler.RegisterWithGet; import app.handler.RegisterWithPost; import exception.ExceptionHandlerMapping; @@ -18,13 +19,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; @@ -71,7 +72,7 @@ public Dispatcher dispatcher() { () -> new Dispatcher( webHandlerList(), handlerAdapterList(), - webHandlerResponseHandlerList() + httpResponseRendererList() ) ); } @@ -82,11 +83,19 @@ public List webHandlerList() { () -> List.of( staticContentHandler(), registerWithGet(), - registerWithPost() + registerWithPost(), + loginWithPost() ) ); } + public StaticContentHandler staticContentHandler() { + return getOrCreate( + "staticContentHandler", + StaticContentHandler::new + ); + } + public RegisterWithGet registerWithGet() { return getOrCreate( "registerWithGet", @@ -101,29 +110,34 @@ public RegisterWithPost registerWithPost() { ); } - public List webHandlerResponseHandlerList() { + public LoginWithPost loginWithPost() { + return getOrCreate("loginWithPost", + () -> new LoginWithPost(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( @@ -228,4 +242,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/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/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/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/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/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..2fab21dcb --- /dev/null +++ b/src/main/java/web/filter/MemberAuthorizationFilter.java @@ -0,0 +1,21 @@ +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()==null) return; + 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..3f91b6a81 --- /dev/null +++ b/src/main/java/web/filter/UnanimousAuthorizationFilter.java @@ -0,0 +1,21 @@ +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()==null) return; + if(request.getAuthenticationInfo().getRole().equals(USER_ROLE)) { + chain.doFilter(); + } else { + response.setStatus(HttpStatus.FOUND); + response.setHeader("Location", "/"); + response.setHeader("Content-Length", "0"); + } + } +} 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; + } +} 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/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; } +} 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); + } +} 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" > 로그인 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" />
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();