Skip to content
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

Authorization 헤더를 응답하도록 변경 #981

Open
wants to merge 16 commits into
base: dev/be
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
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;
import org.springframework.web.context.request.NativeWebRequest;
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<CredentialManager> credentialManagers;
kyum-q marked this conversation as resolved.
Show resolved Hide resolved
private final CredentialProvider credentialProvider;

@Override
Expand All @@ -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(eachCredentialManager -> eachCredentialManager.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(credentialManager -> credentialManager.hasCredential(request));
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package codezap.auth.configuration;

import codezap.auth.provider.CredentialProvider;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
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<CredentialManager> credentialManagers;
private final CredentialProvider credentialProvider;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthArgumentResolver(credentialManager, credentialProvider));
resolvers.add(new AuthArgumentResolver(credentialManagers, credentialProvider));
}
}
44 changes: 30 additions & 14 deletions backend/src/main/java/codezap/auth/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -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<CredentialManager> credentialManagers;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리스트는 혹시 페어할 때 이야기 나누었던 것처럼 Adapter로 발전할 예정일까요?
Adapter 같은 일급 컬렉션으로 추출되면 적절한 CredentialManager를 반환하는지 테스트하기 더 좋을 것 같네요👍

Copy link
Contributor Author

@zangsu zangsu Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아마 프론트 코드 변경 이후엔 다시 List<CredentialManager> -> CredentialManager 로 변경될 것 같아요. 그래서 지금 예상은 Adapter 로 변경되지 않을 것 같네용

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오홍... 뭔가 우리 클라이언트의 환경이 무려 4개(웹, 익스텐션, vs 플러그인, intellij 플러그인)이기도 하고 ....!
호환성을 생각한다면 Mapping 객체를 하나 두는 게 더 낫다고 생각하긴 하는데 짱수는 어떤가요...!

+) 다시 생각해보니 객체 이름은 Adapter보다는 Mapping이 더 의미가 맞는 것 같아요 정정할게요!
(스프링 MVC 미션할 때 구현했던 HandlerMapping에서 착안)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하,,, 그렇군요!!
그렇다면 지금처럼 여러개의 CredentialManager가 공존해야 하는 상황이 지속될 수 있겠네용~~
Mapping 객체 하나 더 두는 방향으로 수정해 보겠습니당~~

private final CredentialProvider credentialProvider;
private final AuthService authService;

Expand All @@ -32,20 +40,28 @@ public ResponseEntity<LoginResponse> login(
) {
LoginMember loginMember = authService.login(loginRequest);
Credential credential = credentialProvider.createCredential(loginMember);
credentialManager.setCredential(httpServletResponse, credential);
credentialManagers.forEach(
credentialManager -> credentialManager.setCredential(httpServletResponse, credential)
);
return ResponseEntity.ok(LoginResponse.from(loginMember));
}

@GetMapping("/login/check")
public ResponseEntity<Void> checkLogin(HttpServletRequest httpServletRequest) {
Credential credential = credentialManager.getCredential(httpServletRequest);
credentialProvider.extractMember(credential);
public ResponseEntity<Void> checkLogin(
@AuthenticationPrinciple Member member,
HttpServletRequest httpServletRequest
) {
if (member == null) {
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "인증 정보가 없습니다. 다시 로그인해 주세요.");
}
return ResponseEntity.ok().build();
}

@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletResponse httpServletResponse) {
credentialManager.removeCredential(httpServletResponse);
credentialManagers.forEach(
credentialManager -> credentialManager.removeCredential(httpServletResponse)
);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -41,7 +42,7 @@ public interface SpringDocAuthController {
@ErrorCase(description = "쿠키 없음", exampleMessage = "쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."),
@ErrorCase(description = "인증 쿠키 없음", exampleMessage = "인증에 대한 쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."),
})
ResponseEntity<Void> checkLogin(HttpServletRequest request);
ResponseEntity<Void> checkLogin(Member member, HttpServletRequest request);

@Operation(summary = "로그아웃")
@ApiResponse(responseCode = "204", description = "인증 성공")
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/java/codezap/auth/dto/LoginMember.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 응답에 설정하기 위한 클래스입니다.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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;

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;
Expand All @@ -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);
}

Expand Down
5 changes: 3 additions & 2 deletions backend/src/main/java/codezap/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down
Loading
Loading