diff --git a/backend/src/main/java/codezap/auth/configuration/AuthArgumentResolver.java b/backend/src/main/java/codezap/auth/configuration/AuthArgumentResolver.java index 36f90531e..c8a8345e5 100644 --- a/backend/src/main/java/codezap/auth/configuration/AuthArgumentResolver.java +++ b/backend/src/main/java/codezap/auth/configuration/AuthArgumentResolver.java @@ -1,12 +1,10 @@ package codezap.auth.configuration; -import codezap.auth.dto.Credential; -import codezap.auth.manager.CredentialManager; -import codezap.auth.provider.CredentialProvider; -import codezap.member.domain.Member; -import jakarta.servlet.http.HttpServletRequest; +import java.util.List; import java.util.Objects; -import lombok.RequiredArgsConstructor; + +import jakarta.servlet.http.HttpServletRequest; + import org.springframework.core.MethodParameter; import org.springframework.lang.NonNull; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -14,10 +12,18 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import codezap.auth.dto.Credential; +import codezap.auth.manager.CredentialManager; +import codezap.auth.provider.CredentialProvider; +import codezap.global.exception.CodeZapException; +import codezap.global.exception.ErrorCode; +import codezap.member.domain.Member; +import lombok.RequiredArgsConstructor; + @RequiredArgsConstructor public class AuthArgumentResolver implements HandlerMethodArgumentResolver { - private final CredentialManager credentialManager; + private final List credentialManagers; private final CredentialProvider credentialProvider; @Override @@ -35,10 +41,19 @@ public Member resolveArgument( AuthenticationPrinciple parameterAnnotation = parameter.getParameterAnnotation(AuthenticationPrinciple.class); boolean supported = Objects.nonNull(parameterAnnotation); HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - if (supported && !parameterAnnotation.required() && !credentialManager.hasCredential(request)) { + if (supported && !parameterAnnotation.required() && !hasCredential(request)) { return null; } + CredentialManager credentialManager = credentialManagers.stream() + .filter(cm -> cm.hasCredential(request)) + .findFirst() + .orElseThrow(() -> new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "인증 정보가 없습니다. 다시 로그인해 주세요.")); Credential credential = credentialManager.getCredential(request); return credentialProvider.extractMember(credential); } + + private boolean hasCredential(HttpServletRequest request) { + return credentialManagers.stream() + .anyMatch(cm -> cm.hasCredential(request)); + } } diff --git a/backend/src/main/java/codezap/auth/configuration/AuthWebConfiguration.java b/backend/src/main/java/codezap/auth/configuration/AuthWebConfiguration.java index fd76b745e..4cd8bdc49 100644 --- a/backend/src/main/java/codezap/auth/configuration/AuthWebConfiguration.java +++ b/backend/src/main/java/codezap/auth/configuration/AuthWebConfiguration.java @@ -1,6 +1,5 @@ package codezap.auth.configuration; -import codezap.auth.provider.CredentialProvider; import java.util.List; import org.springframework.context.annotation.Configuration; @@ -8,17 +7,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import codezap.auth.manager.CredentialManager; +import codezap.auth.provider.CredentialProvider; import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor public class AuthWebConfiguration implements WebMvcConfigurer { - private final CredentialManager credentialManager; + private final List credentialManagers; private final CredentialProvider credentialProvider; @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(new AuthArgumentResolver(credentialManager, credentialProvider)); + resolvers.add(new AuthArgumentResolver(credentialManagers, credentialProvider)); } } diff --git a/backend/src/main/java/codezap/auth/controller/AuthController.java b/backend/src/main/java/codezap/auth/controller/AuthController.java index 22c85ba4b..ff235bff7 100644 --- a/backend/src/main/java/codezap/auth/controller/AuthController.java +++ b/backend/src/main/java/codezap/auth/controller/AuthController.java @@ -1,27 +1,35 @@ package codezap.auth.controller; -import codezap.auth.dto.LoginMember; -import codezap.auth.dto.request.LoginRequest; -import codezap.auth.dto.response.LoginResponse; -import codezap.auth.dto.Credential; -import codezap.auth.manager.CredentialManager; -import codezap.auth.provider.CredentialProvider; -import codezap.auth.service.AuthService; +import java.util.List; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import codezap.auth.configuration.AuthenticationPrinciple; +import codezap.auth.dto.Credential; +import codezap.auth.dto.LoginMember; +import codezap.auth.dto.request.LoginRequest; +import codezap.auth.dto.response.LoginResponse; +import codezap.auth.manager.CredentialManager; +import codezap.auth.provider.CredentialProvider; +import codezap.auth.service.AuthService; +import codezap.global.exception.CodeZapException; +import codezap.global.exception.ErrorCode; +import codezap.member.domain.Member; +import lombok.RequiredArgsConstructor; + @RestController @RequiredArgsConstructor public class AuthController implements SpringDocAuthController { - private final CredentialManager credentialManager; + private final List credentialManagers; private final CredentialProvider credentialProvider; private final AuthService authService; @@ -32,20 +40,24 @@ public ResponseEntity login( ) { LoginMember loginMember = authService.login(loginRequest); Credential credential = credentialProvider.createCredential(loginMember); - credentialManager.setCredential(httpServletResponse, credential); + credentialManagers.forEach(cm -> cm.setCredential(httpServletResponse, credential)); return ResponseEntity.ok(LoginResponse.from(loginMember)); } @GetMapping("/login/check") - public ResponseEntity checkLogin(HttpServletRequest httpServletRequest) { - Credential credential = credentialManager.getCredential(httpServletRequest); - credentialProvider.extractMember(credential); + public ResponseEntity checkLogin( + @AuthenticationPrinciple Member member, + HttpServletRequest httpServletRequest + ) { + if (member == null) { + throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "인증 정보가 없습니다. 다시 로그인해 주세요."); + } return ResponseEntity.ok().build(); } @PostMapping("/logout") public ResponseEntity logout(HttpServletResponse httpServletResponse) { - credentialManager.removeCredential(httpServletResponse); + credentialManagers.forEach(cm -> cm.removeCredential(httpServletResponse)); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/codezap/auth/controller/SpringDocAuthController.java b/backend/src/main/java/codezap/auth/controller/SpringDocAuthController.java index 9bfe6c96b..bc42df75f 100644 --- a/backend/src/main/java/codezap/auth/controller/SpringDocAuthController.java +++ b/backend/src/main/java/codezap/auth/controller/SpringDocAuthController.java @@ -10,6 +10,7 @@ import codezap.auth.dto.response.LoginResponse; import codezap.global.swagger.error.ApiErrorResponse; import codezap.global.swagger.error.ErrorCase; +import codezap.member.domain.Member; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -41,7 +42,7 @@ public interface SpringDocAuthController { @ErrorCase(description = "쿠키 없음", exampleMessage = "쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."), @ErrorCase(description = "인증 쿠키 없음", exampleMessage = "인증에 대한 쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."), }) - ResponseEntity checkLogin(HttpServletRequest request); + ResponseEntity checkLogin(Member member, HttpServletRequest request); @Operation(summary = "로그아웃") @ApiResponse(responseCode = "204", description = "인증 성공") diff --git a/backend/src/main/java/codezap/auth/dto/LoginMember.java b/backend/src/main/java/codezap/auth/dto/LoginMember.java index 30bdd87fb..ac3809cd5 100644 --- a/backend/src/main/java/codezap/auth/dto/LoginMember.java +++ b/backend/src/main/java/codezap/auth/dto/LoginMember.java @@ -2,12 +2,12 @@ import codezap.member.domain.Member; -public record LoginMember ( +public record LoginMember( long id, String name, String password, String salt -){ +) { public static LoginMember from(Member member) { return new LoginMember(member.getId(), member.getName(), member.getPassword(), member.getSalt()); } diff --git a/backend/src/main/java/codezap/auth/manager/AuthorizationHeaderCredentialManager.java b/backend/src/main/java/codezap/auth/manager/AuthorizationHeaderCredentialManager.java index 8e0a73799..c7f195559 100644 --- a/backend/src/main/java/codezap/auth/manager/AuthorizationHeaderCredentialManager.java +++ b/backend/src/main/java/codezap/auth/manager/AuthorizationHeaderCredentialManager.java @@ -1,13 +1,17 @@ package codezap.auth.manager; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; + import codezap.auth.dto.Credential; import codezap.global.exception.CodeZapException; import codezap.global.exception.ErrorCode; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; +@Component @RequiredArgsConstructor public class AuthorizationHeaderCredentialManager implements CredentialManager { diff --git a/backend/src/main/java/codezap/auth/manager/CookieCredentialManager.java b/backend/src/main/java/codezap/auth/manager/CookieCredentialManager.java index b01c2a712..e57fcfbf7 100644 --- a/backend/src/main/java/codezap/auth/manager/CookieCredentialManager.java +++ b/backend/src/main/java/codezap/auth/manager/CookieCredentialManager.java @@ -1,20 +1,20 @@ package codezap.auth.manager; -import codezap.auth.dto.Credential; import java.util.Arrays; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import org.springframework.boot.web.server.Cookie.SameSite; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; +import codezap.auth.dto.Credential; import codezap.global.exception.CodeZapException; import codezap.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; @Component @RequiredArgsConstructor diff --git a/backend/src/main/java/codezap/auth/manager/CredentialManager.java b/backend/src/main/java/codezap/auth/manager/CredentialManager.java index 5bd78998a..b0b942b63 100644 --- a/backend/src/main/java/codezap/auth/manager/CredentialManager.java +++ b/backend/src/main/java/codezap/auth/manager/CredentialManager.java @@ -1,9 +1,10 @@ package codezap.auth.manager; -import codezap.auth.dto.Credential; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import codezap.auth.dto.Credential; + /** * Credential 정보를 Http 응답에 설정하기 위한 클래스입니다. */ diff --git a/backend/src/main/java/codezap/auth/provider/CredentialProvider.java b/backend/src/main/java/codezap/auth/provider/CredentialProvider.java index 0e7b6c8d7..06c705a10 100644 --- a/backend/src/main/java/codezap/auth/provider/CredentialProvider.java +++ b/backend/src/main/java/codezap/auth/provider/CredentialProvider.java @@ -1,7 +1,7 @@ package codezap.auth.provider; -import codezap.auth.dto.LoginMember; import codezap.auth.dto.Credential; +import codezap.auth.dto.LoginMember; import codezap.member.domain.Member; /** diff --git a/backend/src/main/java/codezap/auth/provider/basic/BasicAuthCredentialProvider.java b/backend/src/main/java/codezap/auth/provider/basic/BasicAuthCredentialProvider.java index 9fc5c2e8d..752e84543 100644 --- a/backend/src/main/java/codezap/auth/provider/basic/BasicAuthCredentialProvider.java +++ b/backend/src/main/java/codezap/auth/provider/basic/BasicAuthCredentialProvider.java @@ -1,7 +1,5 @@ package codezap.auth.provider.basic; -import codezap.auth.dto.LoginMember; -import codezap.auth.dto.Credential; import java.nio.charset.StandardCharsets; import jakarta.servlet.http.HttpServletRequest; @@ -9,6 +7,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; +import codezap.auth.dto.Credential; +import codezap.auth.dto.LoginMember; import codezap.auth.provider.CredentialProvider; import codezap.global.exception.CodeZapException; import codezap.global.exception.ErrorCode; @@ -24,7 +24,8 @@ public class BasicAuthCredentialProvider implements CredentialProvider { @Override public Credential createCredential(LoginMember loginMember) { - String credentialValue = HttpHeaders.encodeBasicAuth(loginMember.name(), loginMember.password(), StandardCharsets.UTF_8); + String credentialValue = HttpHeaders.encodeBasicAuth(loginMember.name(), loginMember.password(), + StandardCharsets.UTF_8); return Credential.basic(credentialValue); } diff --git a/backend/src/main/java/codezap/auth/service/AuthService.java b/backend/src/main/java/codezap/auth/service/AuthService.java index e08ff3a81..b83545ef8 100644 --- a/backend/src/main/java/codezap/auth/service/AuthService.java +++ b/backend/src/main/java/codezap/auth/service/AuthService.java @@ -1,5 +1,8 @@ package codezap.auth.service; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import codezap.auth.dto.LoginMember; import codezap.auth.dto.request.LoginRequest; import codezap.auth.encryption.PasswordEncryptor; @@ -8,8 +11,6 @@ import codezap.member.domain.Member; import codezap.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor diff --git a/backend/src/test/java/codezap/auth/configuration/AuthArgumentResolverTest.java b/backend/src/test/java/codezap/auth/configuration/AuthArgumentResolverTest.java index d5157996d..f954e8b15 100644 --- a/backend/src/test/java/codezap/auth/configuration/AuthArgumentResolverTest.java +++ b/backend/src/test/java/codezap/auth/configuration/AuthArgumentResolverTest.java @@ -5,8 +5,10 @@ import codezap.auth.dto.LoginMember; import codezap.auth.dto.Credential; +import codezap.auth.manager.AuthorizationHeaderCredentialManager; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -28,8 +30,10 @@ class AuthArgumentResolverTest { private final CredentialProvider credentialProvider = new PlainCredentialProvider(); - private final CredentialManager credentialManager = new CookieCredentialManager(); - private final AuthArgumentResolver authArgumentResolver = new AuthArgumentResolver(credentialManager, credentialProvider); + private final List credentialManagers = + List.of(new CookieCredentialManager(), new AuthorizationHeaderCredentialManager()); + + private final AuthArgumentResolver authArgumentResolver = new AuthArgumentResolver(credentialManagers, credentialProvider); @Nested @DisplayName("지원하는 파라미터 테스트") @@ -131,7 +135,7 @@ void noCredentialTest() { //when & then assertThatThrownBy(() -> resolveArgument(requiredMethod, nativeWebRequest)) .isInstanceOf(CodeZapException.class) - .hasMessage("쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."); + .hasMessage("인증 정보가 없습니다. 다시 로그인해 주세요."); } @Test @@ -159,7 +163,7 @@ private Member resolveArgument(Method method, NativeWebRequest webRequest) { private void setCredentialCookie(MockHttpServletRequest request, Member member) { MockHttpServletResponse mockResponse = new MockHttpServletResponse(); Credential credential = credentialProvider.createCredential(LoginMember.from(member)); - credentialManager.setCredential(mockResponse, credential); + credentialManagers.forEach(cm -> cm.setCredential(mockResponse, credential)); request.setCookies(mockResponse.getCookies()); } }