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
6 changes: 3 additions & 3 deletions src/main/java/app/handler/LoginWithPost.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ public HandlerResponse handle(QueryParameters params) {
user.getUserRole(),
user.getNickname());

RedirectResponse res = RedirectResponse.to("/");
res.setCookie(
RedirectResponse response = RedirectResponse.to("/");
response.setCookie(
CookieBuilder.of("SID", session.getId())
.path("/")
.httpOnly()
.sameSite(CookieBuilder.SameSite.LAX)
.maxAge(VariableConfig.ABSOLUTE_MS)
);
return res;
return response;
}
}
28 changes: 28 additions & 0 deletions src/main/java/app/handler/LogoutWithPost.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package app.handler;

import http.HttpMethod;
import http.request.HttpRequest;
import http.response.CookieBuilder;
import web.handler.SingleArgHandler;
import web.response.HandlerResponse;
import web.response.RedirectResponse;
import web.session.SessionStorage;

public class LogoutWithPost extends SingleArgHandler<HttpRequest> {
private final SessionStorage sessionManager;

public LogoutWithPost(SessionStorage sessionManager) {
super(HttpMethod.POST, "/user/logout");
this.sessionManager = sessionManager;
}

@Override
public HandlerResponse handle(HttpRequest request) {
String sid = request.getCookieValue("SID").orElse(null);
Comment on lines +20 to +21

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잠재적 동시성 문제: getCookieValue() 호출 후에 sessionManager.invalidate()를 호출하는데, 이 사이에 다른 요청이 같은 세션 ID로 진입할 수 있습니다. 또한 로그아웃 처리 후 쿠키 삭제까지의 과정에서 TOCTOU(Time-of-check-time-of-use) 문제가 존재할 수 있습니다. 세션 무효화 실패 여부를 확인하고 에러 핸들링을 추가하는 것이 좋습니다.

if (sid != null) sessionManager.invalidate(sid);
Comment on lines +20 to +22

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSRF 취약점: 로그아웃 폼이 CSRF 토큰 없이 POST 요청을 수락하고 있습니다. 헤더.html 의 폼에서 CSRF 토큰을 포함시키고, 핸들러에서 이를 검증해야 합니다.


RedirectResponse response = RedirectResponse.to("/");
response.setCookie(CookieBuilder.delete("SID").path("/"));
return response;
}
}
11 changes: 7 additions & 4 deletions src/main/java/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package config;

import app.handler.HomeHandler;
import app.handler.LoginWithPost;
import app.handler.RegisterWithGet;
import app.handler.RegisterWithPost;
import app.handler.*;
import exception.ExceptionHandlerMapping;
import exception.handler.ErrorExceptionHandler;
import exception.handler.ServiceExceptionHandler;
Expand Down Expand Up @@ -90,6 +87,7 @@ public List<WebHandler> webHandlerList() {
registerWithGet(),
registerWithPost(),
loginWithPost(),
logoutWithPost(),
homeHandler())
);
}
Expand Down Expand Up @@ -120,6 +118,11 @@ public LoginWithPost loginWithPost() {
() -> new LoginWithPost(sessionStorage()));
}

public LogoutWithPost logoutWithPost(){
return getOrCreate("logoutWithPost",
() -> new LogoutWithPost(sessionStorage()));
}

public HomeHandler homeHandler(){
return getOrCreate("homeHandler", HomeHandler::new);
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/http/response/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ public void setBody(File file, byte[] body) {
setHeader("Content-Length", String.valueOf(body.length));
}

public void redirectTo(String path){
setStatus(HttpStatus.FOUND);
setHeader("Location", path);
setHeader("Content-Length", "0");
}
Comment on lines +78 to +82

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

경로 검증 누락: redirectTo() 메서드가 경로 입력값을 검증하지 않습니다. 이 메서드를 사용하는 곳에서 값이 제어되지 않은 입력을 받으면 오픈 리다이렉트 취약점이 발생할 수 있습니다. 경로가 상대 경로(또는 같은 호스트)인지 검증하거나, URL 파싱을 통해 악의적 리다이렉트를 방지하세요.


private String guessContentType(File file) {
String byName = URLConnection.guessContentTypeFromName(file.getName());
if (byName != null) return byName;
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/web/filter/MemberAuthorizationFilter.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package web.filter;

import http.HttpStatus;
import http.request.HttpRequest;
import http.response.HttpResponse;
import web.filter.authentication.UserRole;
Expand All @@ -13,9 +12,7 @@ public void runFilter(HttpRequest request, HttpResponse response, FilterChainCon
if(request.getAuthenticationInfo().getRole().equals(UserRole.MEMBER)){
chain.doFilter();
} else {
response.setStatus(HttpStatus.FOUND);
response.setHeader("Location", "/login");
response.setHeader("Content-Length", "0");
response.redirectTo("/login");
}
}
}
3 changes: 3 additions & 0 deletions src/main/resources/templates/layout/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<li class="header__menu__item">
<span class="header__menu__nickname">닉네임: {{userNickname}}</span>
</li>
<form action="/user/logout" method="POST">
<button type="submit">로그아웃</button>
</form>
Comment on lines +9 to +11

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSRF 토큰 누락: 로그아웃 폼에서 CSRF 방지 토큰이 없습니다. 악의적 웹사이트에서 사용자를 공격할 수 있습니다. 숨겨진 입력 필드로 CSRF 토큰을 추가하세요.

{{else1}}
<li class="header__menu__item">
<a class="btn btn_contained btn_size_s" href="/login">로그인</a>
Expand Down