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
7 changes: 2 additions & 5 deletions src/docs/asciidoc/auth-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ include::{snippetsDir}/kakaoLogin/4/http-response.adoc[]

=== **3. 로그아웃**

로그아웃 api 입니다. 요청 헤더에 Key=Cookie, Value="SESSION=" 형식의 세션 쿠키가 존재해야합니다.
로그아웃 api 입니다.

==== Request
include::{snippetsDir}/logout/1/http-request.adoc[]
Expand All @@ -68,13 +68,10 @@ include::{snippetsDir}/logout/1/http-response.adoc[]
==== Response Body Fields
include::{snippetsDir}/logout/1/response-fields.adoc[]

==== 실패 Response
include::{snippetsDir}/logout/2/http-response.adoc[]


=== **4. Session 유효성 확인 API**

Session 유효성 확인 api입니다.
Session 유효성 확인 api 입니다.

==== Request
include::{snippetsDir}/sessionValidityCheck/1/http-request.adoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.ftm.server.adapter.in.web.auth.controller;

import com.ftm.server.application.port.in.auth.LogoutUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -14,13 +16,19 @@
@RequiredArgsConstructor
public class LogoutController {

private final LogoutUseCase logoutUseCase;

@PostMapping("/api/auth/logout")
public ResponseEntity<ApiResponse<Void>> logout(HttpServletRequest req) {
public ResponseEntity<ApiResponse<Void>> logout(
HttpServletRequest req, HttpServletResponse res) {
HttpSession session = req.getSession(false);
if (session != null) {
session.invalidate();
}

// 클라이언트 세션 쿠키 초기화
res.addCookie(logoutUseCase.execute());

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessResponseCode.OK));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.application.port.in.auth;

import com.ftm.server.common.annotation.UseCase;
import jakarta.servlet.http.Cookie;

@UseCase
public interface LogoutUseCase {

Cookie execute();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ftm.server.application.service.auth;

import static com.ftm.server.common.consts.StaticConsts.CLIENT_SESSION_COOKIE_NAME;

import com.ftm.server.application.port.in.auth.LogoutUseCase;
import com.ftm.server.common.utils.CookieUtils;
import jakarta.servlet.http.Cookie;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LogoutService implements LogoutUseCase {

@Override
public Cookie execute() {
// 클라이언트 세션 쿠키 초기화
return CookieUtils.invalidateCookie(CLIENT_SESSION_COOKIE_NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class StaticConsts {
public static final int MINIMUM_DAYS_BETWEEN_TESTS = 7;
public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer ";
public static final String AUTHORIZATION_GRANT_TYPE = "authorization_code";
public static final String CLIENT_SESSION_COOKIE_NAME = "SESSION";
public static final String PENDING_SOCIAL_USER_SESSION_KEY = "PENDING_SOCIAL_USER_INFO";
public static final int PENDING_SOCIAL_USER_SESSION_TTL = 300; // 5분
public static final String GROOMING_TESTS_INFO_CACHE_NAME = "ftm:grooming:tests:info";
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/ftm/server/common/utils/CookieUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ftm.server.common.utils;

import jakarta.servlet.http.Cookie;

public class CookieUtils {

public static Cookie invalidateCookie(String name) {
Cookie cookie = new Cookie(name, null);
cookie.setPath("/");
cookie.setMaxAge(0);
cookie.setHttpOnly(true);
cookie.setSecure(true);

return cookie;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class SecurityConfig {
"/api/users/email/authentication",
"/api/users/email/authentication/code",
"/api/auth/login/**",
"api/auth/logout",
"/api/users",
"/api/users/social",
"/api/grooming/tests/submission",
Expand Down
63 changes: 7 additions & 56 deletions src/test/java/com/ftm/server/auth/LogoutTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.ftm.server.auth;

import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
Expand All @@ -10,28 +9,20 @@
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.ftm.server.BaseTest;
import com.ftm.server.adapter.in.web.auth.dto.request.GeneralLoginRequest;
import com.ftm.server.application.command.user.GeneralUserCreationCommand;
import com.ftm.server.application.port.out.persistence.user.SaveUserImagePort;
import com.ftm.server.application.port.out.persistence.user.SaveUserPort;
import com.ftm.server.application.port.out.security.SecurityAuthenticationPort;
import com.ftm.server.common.response.enums.ErrorResponseCode;
import com.ftm.server.domain.entity.User;
import com.ftm.server.domain.entity.UserImage;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -48,6 +39,11 @@ public class LogoutTest extends BaseTest {
fieldWithPath("message").type(STRING).description("메시지"),
fieldWithPath("data").type(OBJECT).optional().description("data"));

private ResultActions getResultActions(MockHttpSession session) throws Exception {
return mockMvc.perform(
RestDocumentationRequestBuilders.post("/api/auth/logout").session(session));
}

private RestDocumentationResultHandler getDocument(Integer identifier) {
return document(
"logout/" + identifier,
Expand All @@ -67,64 +63,19 @@ private RestDocumentationResultHandler getDocument(Integer identifier) {
.build()));
}

@BeforeEach
void setUp() {
GeneralUserCreationCommand command =
new GeneralUserCreationCommand(
"test@gmail.com",
securityAuthenticationPort.passwordEncode("test1234!"),
"test",
null,
null);
User testUser = saveUserPort.saveUser(User.createGeneralUser(command));
saveUserImagePort.saveUserDefaultImage(UserImage.createUserImage(testUser.getId()));
}

@Test
@Transactional
void 로그아웃_성공() throws Exception {
// given
GeneralLoginRequest request = new GeneralLoginRequest("test@gmail.com", "test1234!");
MockHttpSession session = createUserAndLogin();

// when
MvcResult loginResult =
mockMvc.perform(
RestDocumentationRequestBuilders.post("/api/auth/login")
.contentType(APPLICATION_JSON_VALUE)
.content(mapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andReturn();
MockHttpSession session = (MockHttpSession) loginResult.getRequest().getSession(false);

ResultActions resultActions =
mockMvc.perform(
RestDocumentationRequestBuilders.post("/api/auth/logout").session(session));

// 요청 헤더에 세션 쿠키 수동 추가 (문서화 통과용)
MvcResult result = resultActions.andReturn();
result.getRequest().addHeader("Cookie", "SESSION=mock-session-id; Path=/; HttpOnly");
ResultActions resultActions = getResultActions(session);

// then
resultActions.andExpect(status().isOk()).andDo(print());

// documentation
resultActions.andDo(getDocument(1));
}

@Test
@Transactional
void 로그아웃_실패() throws Exception {
// when
ResultActions resultActions =
mockMvc.perform(RestDocumentationRequestBuilders.post("/api/auth/logout"));

// then
resultActions
.andExpect(status().is(ErrorResponseCode.NOT_AUTHENTICATED.getHttpStatus().value()))
.andExpect(jsonPath("code").value(ErrorResponseCode.NOT_AUTHENTICATED.getCode()))
.andDo(print());

// documentation
resultActions.andDo(getDocument(2));
}
}
Loading