From 6431f16a991ed50a2e502ecd975196d997f5b66b Mon Sep 17 00:00:00 2001 From: 7ijin01 Date: Mon, 15 Sep 2025 21:10:22 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=95=84=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/jwt/CustomLogoutFilter.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java diff --git a/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java b/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java new file mode 100644 index 0000000..dfea038 --- /dev/null +++ b/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java @@ -0,0 +1,67 @@ +package com.opendata.global.jwt; + +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.filter.GenericFilterBean; + +import java.io.IOException; + +@RequiredArgsConstructor +public class CustomLogoutFilter extends GenericFilterBean { + + private final JwtUtil jwtUtil; + + @Override + public void doFilter( + jakarta.servlet.ServletRequest servletRequest, + jakarta.servlet.ServletResponse servletResponse, + FilterChain filterChain + ) throws IOException, ServletException { + doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain); + } + + private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + String requestUri = request.getRequestURI(); + if (!requestUri.equals("/logout") && !requestUri.equals("/api/logout")) { + chain.doFilter(request, response); + return; + } + + String requestMethod = request.getMethod(); + if (!requestMethod.equals("POST")) { + chain.doFilter(request, response); + return; + } + + String refresh = CookieUtil.getRefreshTokenFromRequest(request).orElse(null); + + if (refresh == null) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + try { + jwtUtil.isExpired(refresh); + } catch (ExpiredJwtException e) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String category = jwtUtil.getCategory(refresh); + if (!"refresh".equals(category)) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + CookieUtil.deleteCookie(request, response, "refresh"); + CookieUtil.deleteCookie(request, response, "access"); + + response.setStatus(HttpServletResponse.SC_OK); + } +} From 5f8db83d50b25cfc3335b623d4d592f5413cfa3b Mon Sep 17 00:00:00 2001 From: 7ijin01 Date: Mon, 15 Sep 2025 21:10:40 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/opendata/global/jwt/JwtUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/opendata/global/jwt/JwtUtil.java b/src/main/java/com/opendata/global/jwt/JwtUtil.java index aa6144e..d552073 100644 --- a/src/main/java/com/opendata/global/jwt/JwtUtil.java +++ b/src/main/java/com/opendata/global/jwt/JwtUtil.java @@ -54,6 +54,10 @@ public String getRole(String token) { return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload() .get("role", String.class); } + public String getCategory(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload() + .get("category", String.class); + } From 05e3725851227460a2de16d61b4e3912ecb93899 Mon Sep 17 00:00:00 2001 From: 7ijin01 Date: Mon, 15 Sep 2025 21:10:54 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opendata/docs/LogoutControllerDocs.java | 20 +++++++++++++++++++ .../user/controller/LogoutController.java | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/main/java/com/opendata/docs/LogoutControllerDocs.java create mode 100644 src/main/java/com/opendata/domain/user/controller/LogoutController.java diff --git a/src/main/java/com/opendata/docs/LogoutControllerDocs.java b/src/main/java/com/opendata/docs/LogoutControllerDocs.java new file mode 100644 index 0000000..0213610 --- /dev/null +++ b/src/main/java/com/opendata/docs/LogoutControllerDocs.java @@ -0,0 +1,20 @@ +package com.opendata.docs; + + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "로그아웃 API") +public interface LogoutControllerDocs { + @Operation(summary = "로그아웃") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "로그아웃 성공"), + @ApiResponse(responseCode = "500", description = "서버 에러") + }) + ResponseEntity doLogout(); +} + diff --git a/src/main/java/com/opendata/domain/user/controller/LogoutController.java b/src/main/java/com/opendata/domain/user/controller/LogoutController.java new file mode 100644 index 0000000..0be0dba --- /dev/null +++ b/src/main/java/com/opendata/domain/user/controller/LogoutController.java @@ -0,0 +1,19 @@ +package com.opendata.domain.user.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.opendata.docs.LogoutControllerDocs; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class LogoutController implements LogoutControllerDocs { + + @PostMapping("/logout") + public ResponseEntity doLogout(){ + return ResponseEntity.ok().build(); + } +} From 1f50f282a2b731d54e513612571460f55169d6eb Mon Sep 17 00:00:00 2001 From: 7ijin01 Date: Mon, 15 Sep 2025 21:11:07 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=ED=95=84=ED=84=B0=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/opendata/global/config/SecurityConfig.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/opendata/global/config/SecurityConfig.java b/src/main/java/com/opendata/global/config/SecurityConfig.java index e7f9401..f534a0f 100644 --- a/src/main/java/com/opendata/global/config/SecurityConfig.java +++ b/src/main/java/com/opendata/global/config/SecurityConfig.java @@ -7,6 +7,7 @@ import com.opendata.domain.oauth2.service.CustomOAuth2UserService; import com.opendata.domain.user.repository.UserRepository; import com.opendata.global.jwt.CookieUtil; +import com.opendata.global.jwt.CustomLogoutFilter; import com.opendata.global.jwt.JwtFilter; import com.opendata.global.jwt.JwtUtil; import com.opendata.global.jwt.LoginFilter; @@ -29,6 +30,7 @@ import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -111,8 +113,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .successHandler(customSuccessHandler) ); - http - .addFilterBefore(new JwtFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(new JwtFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); + + http.addFilterBefore(new CustomLogoutFilter(jwtUtil), LogoutFilter.class); // .addFilterBefore(oAuth2RedirectUriCookieFilter, OAuth2AuthorizationRequestRedirectFilter.class); // .addFilterAfter(new JwtFilter(jwtUtil,userDetailsService), OAuth2LoginAuthenticationFilter.class); // .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, cookieUtil, userRepository), UsernamePasswordAuthenticationFilter.class); From b8ce8787c7162a9a5fc4b0d7548fbed925dbaf02 Mon Sep 17 00:00:00 2001 From: 7ijin01 Date: Mon, 15 Sep 2025 21:11:37 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EB=B0=B0=ED=8F=AC=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77d85e1..e08469a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: CI / CD on: push: - branches: [main] + branches: [feat/#85-logout] jobs: CI: From 8217e54c527cf555b554780321307e667796f6e5 Mon Sep 17 00:00:00 2001 From: 7ijin01 Date: Mon, 15 Sep 2025 22:29:47 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/opendata/global/jwt/CustomLogoutFilter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java b/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java index dfea038..637aa3b 100644 --- a/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java +++ b/src/main/java/com/opendata/global/jwt/CustomLogoutFilter.java @@ -59,8 +59,11 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response, return; } - CookieUtil.deleteCookie(request, response, "refresh"); - CookieUtil.deleteCookie(request, response, "access"); + String domain = ".yourse-seoul.com"; + response.addHeader("Set-Cookie", + "refresh=; Max-Age=0; Path=/; Domain=" + domain + "; HttpOnly; Secure; SameSite=Strict"); + response.addHeader("Set-Cookie", + "access=; Max-Age=0; Path=/; Domain=" + domain + "; HttpOnly; Secure; SameSite=Strict"); response.setStatus(HttpServletResponse.SC_OK); }