Skip to content

[인증(Authentication) - 1단계] 김선호 미션 제출합니다. 🚀 #40

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

Merged
merged 20 commits into from
Feb 9, 2025

Conversation

haero77
Copy link

@haero77 haero77 commented Feb 8, 2025

안녕하세요! [1단계 - SecurityFilterChain 구현] 리뷰 요청드립니다.

첫 오프라인 수업 듣고도 생각보다 막막하고 어려워서,
새 프로젝트에 Spring Security 의존성을 주입받아서 브레이크 포인트 찍고
FilterChainProxy, DelegatingFilterProxy의 등록 과정을 하나씩 살펴가면서 공부했습니다.

때문에 미션에 10시간 넘게 투자했음에도 겨우 step1만 구현했지만,
Security 초기화 과정에 대해 조금이나마 이해할 수 있게되어 긍정적으로 생각하고 있습니다..!

Security 코드를 뜯어가며 공부하다보니, Security와 비슷하게 구현한 부분이 제법 있습니다.
비슷하게 구현하면서도 제가 추구하는 스타일에 맞게 조금씩 변주를 해봤습니다.
(커밋을 순서대로 읽어나가시면 보다 편하게 리뷰 하실 수 있을거라고 생각해요..!)

리뷰 완료되면 피드백하며 2, 3, 4단계는 한 번에 올리도록 하겠습니다.
조금 늦더라도 끝까지 가보겠습니다..! 감사합니다!


🙇‍♂️ 중점적으로 봐주셨으면 하는 부분

1. 요구사항 준수와 오버 엔지니어링 여부

  • 제가 놓친 요구사항이 없을지 궁금합니다.
  • 또, 요구사항에 비해 과하게 구현/설계한 부분이 있는지 궁금합니다.

2. 인증을 잘 구현했는지에 대한 여부

  • 인증용 필터를 만들어보는 과정이 처음이기 때문에, 제시된 테스트는 통과하지만 예상하지 못한 사이드 이펙트가 있을지 궁금합니다.

🤔 미션을 진행하며 고민했던 내용

이번 미션을 진행하며 다음의 항목들을 고민했으며, 관련해서 궁금한 부분 코멘트로 질문 남겨두겠습니다..!

  • 왜 인증/인가 처리를 인터셉터가 아닌 필터에서 해야할까?
  • Spring Security는 왜 FilterChainProxy에 굳이 VitualFilterChain을 만들어야했을까?

💡 이번 미션을 진행하며 느낀점

  • Spring Security 코드를 아주 조금 맛볼 수 있게되어(?) 어렵지만 재밌네요..!

- 인증 로직이 서비스 로직에 의존하지 않도록 중간 객체를 이요하여 의존성 분리
- 인터셉터는 Spring MVC에 속하고 이것은 app로직으로 볼 수 있으므로 비즈니스 로직과 인증/인가를 분리하기 위해 인터셉터가 아닌 필터에서 처리하도록 변경
- 필터의 예외처리는 Spring MVC 예외처리에 포함되지 않으므로 ResponseStatus는 동작 안 함
…rProxy를 필터로 등록하도록 수정

- springSecurityFilterChain 이름의 빈(=filterChainProxy)이 없으면 DelegatingFilterProxy를 필터로 등록할 필요가 없다.
@haero77 haero77 changed the title [인증(Authentication)] 김선호 미션 제출합니다. [인증(Authentication)] 김선호 미션 제출합니다. 🚀 Feb 8, 2025
Comment on lines +12 to +13
public class FormLoginFilter implements Filter {

Copy link
Author

@haero77 haero77 Feb 8, 2025

Choose a reason for hiding this comment

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

왜 인증/인가 처리를 인터셉터가 아닌 필터에서 해야할까?

처음에 컨트롤러로 구현된 인증 로직을 인터셉터로 리팩터링하고,
이 로직을 다시 필터로 리팩토링하게 하는 것은 분명 미션 설계자의 분명한 의도가 있을 것이라고 생각했습니다.

인터셉터로 만들면 DispatcherServlet의 예외 처리 등 스프링 컨텍스트의 도움도 받고 좋을텐데,
왜 굳이 인증 로직을 필터로 처리해야하는 걸까?

여기에 대한 제가 내린 나름의 근거는 다음과 같습니다.

  • Spring MVC을 활용해서 개발자가 작성하는 코드는 서비스(비즈니스) 로직이다.
  • 인증/인가는 서비스 로직은 아니기 때문에 Spring MVC의 도움을 받을 필요가 없고, 그러니까 인터셉터가 아니라 필터로 구현하는게 낫다.
  • DispatcherServlet 이후는 서비스 로직이기 때문에, DispatcherServlet 에 요청이 닿기 전에 HTTP 요청을 끝내는게 맞는 방향.

먼저 리뷰어님은 여기에 대해 어떻게 생각하시는지 궁금합니다!

또, 단순히 유저의 권한에 따라 API 요청 자체를 차단하는 것이 아닌, DB에 저장된 구체적인 데이터 상태를 보고나서 요청을 처리할지 말지를 결정하는 것은 권한이 아니라 비즈니스 로직으로 보는 건지 궁금합니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interceptor를 사용하게 되는 경우 톰캣을 거친 후 동작하는 인증을 커버할 수 있는데
Filter로 하는 경우 톰캣으로 동작하기 전에 인증을 할 수 있는 차이가 있습니다.

아무래도 톰캣으로 들어오는 요청 외 정적 리소스들에 대한 인증을 고려해본다면 조금 더 폭넓은 인증 범위를 커버할 수 있을 것 같네요!

Copy link
Contributor

@boorownie boorownie left a comment

Choose a reason for hiding this comment

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

1단계 기능 구현 잘 해주셨습니다!
질문 주신 부분에 대해서는 코멘트로 남겨두었으니 확인해주시고
이어서 2,3,4 단계 미션 진행해주세요~

* "springSecurityFilterChain"이라는 이름으로 FilterChainProxy를 빈으로 등록한다.
*/
@Configuration
public class WebSecurityConfiguration {
Copy link
Contributor

Choose a reason for hiding this comment

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

오버 엔지니어링은 아니고 다만 다른 주차의 미션 요구사항을 미리 하신 느낌이에요!

왜 1단계 하는데 10시간이나 걸렸지? 라고 생각했는데
이 부분까지 고려해서 미션을 해주셨군요!ㅋㅋㅋ
사실 이 부분은 4주차에서 진행하려고 했던 부분이긴 합니다만 삽질과 노가다를 통해 이해하셨다니 정말 대단합니다!ㅎㅎ

import java.io.IOException;
import java.util.Optional;

public class BasicAuthenticationFilter implements Filter {
Copy link
Contributor

Choose a reason for hiding this comment

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

Filter 대신 OncePerRequestFilter 이나 GenericFilterBean을 고려해보면 어떨까요?
어떤 차이가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

이 코멘트에서 해당 리뷰 반영해봤습니다 :)

Comment on lines +18 to +36
@Bean
public SecurityFilterChain formLoginSecurityFilterChain() {
return new DefaultSecurityFilterChain(
(httpServletRequest) -> httpServletRequest.getRequestURI().equals("/login"),
List.of(
new FormLoginFilter(userDetailsService)
)
);
}

@Bean
public SecurityFilterChain basicAuthenticationSecurityFilterChain() {
return new DefaultSecurityFilterChain(
(httpServletRequest) -> httpServletRequest.getRequestURI().equals("/members"),
List.of(
new BasicAuthenticationFilter(userDetailsService)
)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

SecurityFilterChain을 각각 만들어 주셨네요!
만약 하나의 SecurityFilterChain으로 만들기 위해서는 어떻게 하면 좋을까요?

Copy link
Author

@haero77 haero77 Feb 13, 2025

Choose a reason for hiding this comment

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

이 코멘트에서 해당 피드백 반영해봤습니다..!

.findFirst();
}

private static final class VirtualFilterChain implements FilterChain {
Copy link
Contributor

Choose a reason for hiding this comment

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

버츄어 필터를 잘 구현해주셨네요 👍

Comment on lines +12 to +13
public class FormLoginFilter implements Filter {

Copy link
Contributor

Choose a reason for hiding this comment

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

Interceptor를 사용하게 되는 경우 톰캣을 거친 후 동작하는 인증을 커버할 수 있는데
Filter로 하는 경우 톰캣으로 동작하기 전에 인증을 할 수 있는 차이가 있습니다.

아무래도 톰캣으로 들어오는 요청 외 정적 리소스들에 대한 인증을 고려해본다면 조금 더 폭넓은 인증 범위를 커버할 수 있을 것 같네요!

@boorownie boorownie merged commit 39bca0f into next-step:haero77 Feb 9, 2025
@haero77 haero77 changed the title [인증(Authentication)] 김선호 미션 제출합니다. 🚀 [인증(Authentication) - 1단계] 김선호 미션 제출합니다. 🚀 Feb 9, 2025
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 12, 2025
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 12, 2025
…FilterChain를 하나만 유지하는 구조로 변경

- applies feedback next-step#40 (comment)
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 12, 2025
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 12, 2025
…FilterChain를 하나만 유지하는 구조로 변경

- applies feedback next-step#40 (comment)
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 12, 2025
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 13, 2025
…FilterChain를 하나만 유지하는 구조로 변경

- applies feedback next-step#40 (comment)
haero77 added a commit to haero77/next-step-spring-security-authentication that referenced this pull request Feb 13, 2025
boorownie pushed a commit that referenced this pull request Feb 20, 2025
* refactor(feedback): API URI 마다 SecurityFilterChain을 생성하지 않도록 SecurityFilterChain를 하나만 유지하는 구조로 변경

- applies feedback #40 (comment)

* feat(step2): 인증 정보(사용자명, 인증 상태 등)를 담는 Authentication 정의 & 구현체 UsernamePasswordAuthenticationToken 구현

* feat: 인증 예외를 의미하는 AuthenticationException 정의

* feat(step2): 구현체 타입에 따라 인증된 Authentication을 리턴하는 AuthenticationProvider 정의

* feat(step2): UsernamePasswordAuthenticationToken의 인증을 지원하는 DaoAuthenticationProvider 구현

* feat(step2): 인증 요청 받은 authentication에 대한 인증을 시도하는 AuthenticationManager 정의

* feat(step2): 주입 받은 AuthenticationProvider를 순회하며 인증을 시도하는 AuthenticationManager 구현체 ProviderManager 구현

* refactor(step2): 폼 로그인 인증 처리를 AuthenticationManager에 위임하도록 변경

* refactor(step2): 폼 로그인 인증을 처리하는 필터의 이름을 UsernamePasswordAuthenticationFilter로 변경

* refactor(step2): Basic 인증 처리를 AuthenticationManager에 위임하도록 변경

* refactor(feedback): 인증 필터가 요청당 한 번만 수행되는 것을 보장하도록 OncePerRequestFilter를 상속하도록 변경

- applies feedback #40 (comment)

* feat(step3): Authentication을 보관하는 SecurityContext 정의, 구현체 SecurityContextImpl 구현

* feat(step3): 각 스레드별로 독립된 SecurityContext를 관리하는 SecurityContextHolder 구현

* refactor(step3): BasicAuthenticationFilter에서 인증된 Authentication을 SecurityContext로 관리하도록 변경

* refactor: 폼 로그인 인증 - username과 password를 추출하여 인증하는 로직을 메서드로 분리

* refactor(step3): HTTP Session을 사용하지 않고 SecurityContextHolder를 통해 인증 정보를 관리하도록 변경

* feat(step4): 사용자의 인증 요청 이후 사용자의 인증을 유지하기 위한 SecurityContextRepository 정의

* feat(step4): 인증을 HTTP Session에 저장하기 위한 HttpSessionSecurityContextRepository 구현

* feat(step4): SecurityContextRepository를 사용하여 SecurityContext를 얻고, 이것을 SecurityContextHolder에 적재하는 SecurityContextHolderFilter 구현

* refactor(step4): 인증 필터에서 인증 후 securityContextRepository에 SecurityContext를 저장하도록 변경

- SecurityContextPersistenceFilter는 securityContextRepository에 항상 context를 저장하지만,SecurityContextHolderFilter는 각 인증 필터에게 securityContextRepository를 저장할지 말지 자율권을 준다.

* test(step4): HTTP Session에 인증 정보 저장 후 인증 유지 테스트 추가
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants