Feat: 로그아웃 기능 추가 및 FCM 토큰 제거

This commit is contained in:
김용수 2024-09-24 13:19:51 +09:00
parent 0ac4c743ed
commit 33a70cf451
4 changed files with 26 additions and 53 deletions

View File

@ -12,7 +12,6 @@ import com.worlabel.global.config.swagger.SwaggerApiError;
import com.worlabel.global.config.swagger.SwaggerApiSuccess; import com.worlabel.global.config.swagger.SwaggerApiSuccess;
import com.worlabel.global.exception.CustomException; import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode; import com.worlabel.global.exception.ErrorCode;
import com.worlabel.global.service.FcmService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
@ -51,7 +50,7 @@ public class AuthController {
JwtToken newToken = authService.reissue(refresh); JwtToken newToken = authService.reissue(refresh);
int id = jwtTokenService.parseId(newToken.getAccessToken()); int id = jwtTokenService.parseId(newToken.getAccessToken());
response.addCookie(createCookie(newToken.getRefreshToken())); response.addCookie(createRefreshCookie(newToken.getRefreshToken(), (int) (refreshExpiry / 1000)));
authService.saveRefreshToken(id, newToken.getRefreshToken(), refreshExpiry); authService.saveRefreshToken(id, newToken.getRefreshToken(), refreshExpiry);
return AccessTokenResponse.from(newToken.getAccessToken()); return AccessTokenResponse.from(newToken.getAccessToken());
@ -62,6 +61,20 @@ public class AuthController {
} }
} }
@Operation(summary = "로그아웃", description = "사용자의 JWT, FCM Token, 리프레시 토큰을 삭제합니다.")
@SwaggerApiSuccess(description = "Logout")
@SwaggerApiError({ErrorCode.INVALID_TOKEN, ErrorCode.INVALID_REFRESH_TOKEN, ErrorCode.USER_NOT_FOUND})
@PostMapping("/logout")
public void logout(@CurrentUser final Integer memberId, HttpServletRequest request, HttpServletResponse response) {
// 쿠키에서 리프레시 토큰 삭제
Cookie deleteCookie = createRefreshCookie(null, 0);
response.addCookie(deleteCookie);
authService.deleteRefreshToken(memberId);
authService.deleteFcmToken(memberId);
}
@Operation(summary = "로그인 중인 사용자 정보를 반환", description = "현재 로그인중인 사용자의 정보를 반환합니다.") @Operation(summary = "로그인 중인 사용자 정보를 반환", description = "현재 로그인중인 사용자의 정보를 반환합니다.")
@SwaggerApiSuccess(description = "Return Member Info") @SwaggerApiSuccess(description = "Return Member Info")
@SwaggerApiError({ErrorCode.INVALID_TOKEN, ErrorCode.INVALID_REFRESH_TOKEN, ErrorCode.USER_NOT_FOUND}) @SwaggerApiError({ErrorCode.INVALID_TOKEN, ErrorCode.INVALID_REFRESH_TOKEN, ErrorCode.USER_NOT_FOUND})
@ -90,9 +103,9 @@ public class AuthController {
return null; return null;
} }
private Cookie createCookie(String value) { private Cookie createRefreshCookie(String value, int time) {
Cookie cookie = new Cookie("refreshToken", value); Cookie cookie = new Cookie("refreshToken", value);
cookie.setMaxAge((int) (refreshExpiry / 1000)); cookie.setMaxAge(time);
cookie.setPath("/"); cookie.setPath("/");
cookie.setHttpOnly(true); cookie.setHttpOnly(true);
cookie.setSecure(true); // 배포 HTTPS에서 사용 cookie.setSecure(true); // 배포 HTTPS에서 사용

View File

@ -1,38 +0,0 @@
package com.worlabel.domain.auth.handler;
import com.worlabel.domain.auth.service.AuthService;
import com.worlabel.domain.auth.service.JwtTokenService;
import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class CustomLogoutHandler implements LogoutHandler {
private AuthService authService;
private JwtTokenService jwtTokenService;
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String token = request.getHeader("Authorization");
try {
String refreshedToken = token.substring(7);
int memberId = jwtTokenService.parseId(refreshedToken);
authService.deleteRefreshToken(memberId);
authService.deleteFcmToken(memberId);
log.debug("로그아웃된 사용자의 토큰이 삭제 됨 {}", memberId);
} catch (Exception e) {
throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN);
}
}
}

View File

@ -14,14 +14,14 @@ public class FcmRepository {
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
public void save(int memberId, String token) { public void save(int memberId, String token) {
redisTemplate.opsForHash().put(CacheKey.fcmTokenKey(), memberId, token); redisTemplate.opsForHash().put(CacheKey.fcmTokenKey(), String.valueOf(memberId), token);
} }
public void delete(int memberId) { public void delete(int memberId) {
redisTemplate.opsForHash().delete(CacheKey.fcmTokenKey(), memberId); redisTemplate.opsForHash().delete(CacheKey.fcmTokenKey(), String.valueOf(memberId));
} }
public String getToken(int memberId) { public String getToken(int memberId) {
return (String) redisTemplate.opsForHash().get(CacheKey.fcmTokenKey(), memberId); return (String) redisTemplate.opsForHash().get(CacheKey.fcmTokenKey(), String.valueOf(memberId));
} }
} }

View File

@ -1,10 +1,8 @@
package com.worlabel.global.config; package com.worlabel.global.config;
import com.worlabel.domain.auth.handler.*;
import com.worlabel.domain.auth.service.CustomOAuth2UserService; import com.worlabel.domain.auth.service.CustomOAuth2UserService;
import com.worlabel.global.filter.JwtAuthenticationFilter; import com.worlabel.global.filter.JwtAuthenticationFilter;
import com.worlabel.domain.auth.handler.CustomAuthenticationDeniedHandler;
import com.worlabel.domain.auth.handler.CustomAuthenticationEntryPoint;
import com.worlabel.domain.auth.handler.OAuth2SuccessHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -24,6 +22,7 @@ import java.util.List;
@EnableWebSecurity @EnableWebSecurity
@RequiredArgsConstructor @RequiredArgsConstructor
public class SecurityConfig { public class SecurityConfig {
private final CustomAuthenticationDeniedHandler authenticationDeniedHandler; private final CustomAuthenticationDeniedHandler authenticationDeniedHandler;
private final CustomAuthenticationEntryPoint authenticationEntryPoint; private final CustomAuthenticationEntryPoint authenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter; private final JwtAuthenticationFilter jwtAuthenticationFilter;
@ -70,7 +69,6 @@ public class SecurityConfig {
.requestMatchers("/swagger", "/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**", "/ws/**").permitAll() .requestMatchers("/swagger", "/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**", "/ws/**").permitAll()
.requestMatchers("/api/auth/reissue").permitAll() .requestMatchers("/api/auth/reissue").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
// .anyRequest().permitAll()
); );
// OAuth2 // OAuth2