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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ public TokenCookieFactory(CookieProperties cookieProperties) {
}

public ResponseCookie refreshTokenCookie(String refreshToken) {
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from("refresh_token", refreshToken)
return refreshTokenCookieBuilder(refreshToken).build();
}

public ResponseCookie deleteRefreshTokenCookie() {
return refreshTokenCookieBuilder("")
.maxAge(0)
.build();
}

private ResponseCookie.ResponseCookieBuilder refreshTokenCookieBuilder(String value) {
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from("refresh_token", value)
.httpOnly(true)
.path("/")
.secure(cookieProperties.isSecure());
Expand All @@ -29,6 +39,6 @@ public ResponseCookie refreshTokenCookie(String refreshToken) {
builder.sameSite(cookieProperties.getSameSite());
}

return builder.build();
return builder;
}
}
16 changes: 16 additions & 0 deletions src/main/java/me/gg/pinit/interfaces/member/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import me.gg.pinit.interfaces.member.dto.LoginResponse;
import me.gg.pinit.interfaces.member.dto.SignupRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -109,6 +110,21 @@ public ResponseEntity<LoginResponse> refresh(HttpServletRequest request) {
.body(new LoginResponse(newAccessToken));
}

@PostMapping("/logout")
@Operation(
summary = "로그아웃",
description = "refresh_token 쿠키를 만료시켜 로그아웃 처리합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그아웃 성공")
})
public ResponseEntity<Void> logout() {
Comment on lines +113 to +121
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

문제점: 로그아웃 엔드포인트에 인증이 요구되지 않습니다. 현재 누구나 이 엔드포인트를 호출하여 다른 사용자의 refresh_token 쿠키를 삭제할 수 있습니다.

영향: 공격자가 임의로 사용자의 로그아웃을 유발할 수 있어 가용성(availability) 측면의 보안 문제가 발생할 수 있습니다.

수정 제안: /me 엔드포인트처럼 @securityrequirement 애노테이션을 추가하여 Bearer 토큰을 통한 인증을 요구하거나, 또는 refresh_token 쿠키의 존재 여부를 검증하는 로직을 추가해야 합니다.

Copilot uses AI. Check for mistakes.
ResponseCookie expiredCookie = tokenCookieFactory.deleteRefreshTokenCookie();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, expiredCookie.toString())
.build();
}

@GetMapping("/me")
@Operation(
summary = "로그인 확인",
Expand Down