-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] : 로그아웃 및 회원탈퇴 기능 구현 #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
f2b816e
be637e6
9996297
f83b904
34b5a19
f832a23
564fba0
58e0f6f
f499194
d2d34d8
9268fb8
752481c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package hanium.modic.backend.domain.auth.util; | ||
|
|
||
| import org.springframework.context.annotation.Profile; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import jakarta.servlet.http.Cookie; | ||
|
|
||
| // 개발 환경용 쿠키 유틸리티 | ||
| @Component | ||
| @Profile({"local", "dev", "test"}) | ||
| public class DevCookieUtil implements CookieUtil { | ||
|
|
||
| private final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; | ||
|
|
||
| private final int COOKIE_MAX_AGE = 60 * 60 * 24 * 3; | ||
yooooonshine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public Cookie createRefreshCookie(final String refreshToken) { | ||
| Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(COOKIE_MAX_AGE); | ||
| return cookie; | ||
| } | ||
yooooonshine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public Cookie deleteRefreshCookie() { | ||
| Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE_NAME, null); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setPath("/"); // 생성 시와 동일해야 함 | ||
| cookie.setMaxAge(0); // 브라우저에서 즉시 삭제 | ||
| return cookie; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package hanium.modic.backend.domain.auth.util; | ||
|
|
||
| import org.springframework.context.annotation.Profile; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import jakarta.servlet.http.Cookie; | ||
|
|
||
| // Production 환경용 쿠키 유틸리티 | ||
| @Component | ||
| @Profile({"main"}) | ||
| public class ProdCookieUtil implements CookieUtil { | ||
|
|
||
| private final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; | ||
|
|
||
| private final int COOKIE_MAX_AGE = 60 * 60 * 24 * 3; | ||
yooooonshine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public Cookie createRefreshCookie(final String refreshToken) { | ||
| Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setPath("/"); | ||
| cookie.setSecure(true); | ||
| cookie.setMaxAge(COOKIE_MAX_AGE); | ||
| return cookie; | ||
| } | ||
yooooonshine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public Cookie deleteRefreshCookie() { | ||
| Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE_NAME, null); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setPath("/"); // 생성 시와 동일해야 함 | ||
| cookie.setSecure(true); | ||
| cookie.setMaxAge(0); // 브라우저에서 즉시 삭제 | ||
| return cookie; | ||
| } | ||
yooooonshine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| package hanium.modic.backend.domain.user.enums; | ||
|
|
||
| public enum UserRole { | ||
| ADMIN, USER | ||
| ADMIN, | ||
| USER, | ||
| WITHDRAWN | ||
yooooonshine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,7 @@ | ||||||||||||
| package hanium.modic.backend.web.auth.controller; | ||||||||||||
|
|
||||||||||||
| import static hanium.modic.backend.common.error.ErrorCode.*; | ||||||||||||
|
|
||||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||||
| import org.springframework.validation.annotation.Validated; | ||||||||||||
| import org.springframework.web.bind.annotation.CookieValue; | ||||||||||||
|
|
@@ -10,7 +12,9 @@ | |||||||||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||||||||||
| import org.springframework.web.bind.annotation.RestController; | ||||||||||||
|
|
||||||||||||
| import hanium.modic.backend.common.jwt.JwtTokenProvider; | ||||||||||||
| import hanium.modic.backend.common.response.AppResponse; | ||||||||||||
| import hanium.modic.backend.common.swagger.ApiErrorMapping; | ||||||||||||
| import hanium.modic.backend.domain.auth.constant.AuthConstant; | ||||||||||||
| import hanium.modic.backend.domain.auth.service.AuthService; | ||||||||||||
| import hanium.modic.backend.domain.auth.util.CookieUtil; | ||||||||||||
|
|
@@ -24,6 +28,7 @@ | |||||||||||
| import io.swagger.v3.oas.annotations.Operation; | ||||||||||||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||||||||||||
| import jakarta.servlet.http.Cookie; | ||||||||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||||
| import jakarta.validation.Valid; | ||||||||||||
| import jakarta.validation.constraints.Email; | ||||||||||||
|
|
@@ -37,6 +42,8 @@ | |||||||||||
| public class AuthController { | ||||||||||||
|
|
||||||||||||
| private final AuthService authService; | ||||||||||||
| private final CookieUtil cookieUtil; | ||||||||||||
| private final JwtTokenProvider jwtTokenProvider; | ||||||||||||
|
|
||||||||||||
| @PostMapping("/login") | ||||||||||||
| @Operation( | ||||||||||||
|
|
@@ -52,12 +59,34 @@ public ResponseEntity<AppResponse<LoginResponse>> login(@RequestBody @Valid Logi | |||||||||||
| LoginResponse loginResponse = authService.login(request.email(), request.password()); | ||||||||||||
|
|
||||||||||||
| response.addHeader(AuthConstant.AUTHORIZATION, AuthConstant.BEARER + loginResponse.accessToken()); | ||||||||||||
| Cookie refreshTokenCookie = CookieUtil.createRefreshCookie(loginResponse.refreshToken()); | ||||||||||||
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(loginResponse.refreshToken()); | ||||||||||||
| response.addCookie(refreshTokenCookie); | ||||||||||||
|
|
||||||||||||
| return ResponseEntity.ok(AppResponse.ok(loginResponse)); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @PostMapping("/logout") | ||||||||||||
| @Operation( | ||||||||||||
| summary = "로그아웃 API", | ||||||||||||
| description = "리프레시 토큰을 통해 로그아웃합니다. 로그아웃된 토큰으로 요청시 [C-005] - 차단된 토큰입니다를 반환합니다." | ||||||||||||
| ) | ||||||||||||
| @ApiErrorMapping({ | ||||||||||||
| USER_NOT_FOUND_EXCEPTION | ||||||||||||
| }) | ||||||||||||
| public ResponseEntity<AppResponse<Void>> logout( | ||||||||||||
| @CookieValue(name = "refreshToken") String refreshToken, | ||||||||||||
| HttpServletRequest request, | ||||||||||||
| HttpServletResponse response | ||||||||||||
| ) { | ||||||||||||
| String accessToken = jwtTokenProvider.extractAccessToken(request).orElse(null); | ||||||||||||
| authService.logout(refreshToken, accessToken); | ||||||||||||
|
|
||||||||||||
|
||||||||||||
| String accessToken = jwtTokenProvider.extractAccessToken(request).orElse(null); | |
| authService.logout(refreshToken, accessToken); | |
| String accessToken = jwtTokenProvider.extractAccessToken(request) | |
| .orElseThrow(() -> new AppException(USER_NOT_AUTHENTICATED_EXCEPTION)); | |
| authService.logout(refreshToken, accessToken); |
🤖 Prompt for AI Agents
In src/main/java/hanium/modic/backend/web/auth/controller/AuthController.java
around lines 85 to 87, the code extracts the access token as an Optional and
passes null to authService.logout when the Authorization header is missing;
instead fail fast: if the access token Optional is empty, throw an
authentication/authorization exception (e.g., BadCredentialsException or a
custom AuthException) immediately rather than passing null, otherwise call
authService.logout with the non-null access token; ensure
jwtTokenProvider.setBlackList is never called with null by enforcing the
presence of the token before calling logout.
Uh oh!
There was an error while loading. Please reload this page.