일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 자바
- bytecode
- 이펙티브 자바
- 파라미터 그룹
- 생각정리
- JVM
- springboot
- Checked Exception
- java
- RDS
- Annotation
- 바이트코드
- exception
- 자바스터디
- Unchecked Exception
- 보안 그룹
- Effective Java
- 예외
- spring-security
- 이펙티브자바
- ec2
- https
- Spring
- Final
- AWS
- error
- 피리티어
- try-with-reources
- Today
- Total
개발 일지
저번 글에서 Spring Security가 무엇이고 인증에 대한 기본 동작에 대해서 살펴보았습니다.
Spring Security는 필터체인를 기반으로 동작한다고 했었는데, 이번글에서는 이 Filter대해 알아보고 실재 코드를 보면서 인등(로그인) 동작을 자세히 정리해 보려고 한다.
Filter
Filter란
- 클라이언트 요청이 Dispatcher Servlet으로 가기 전에 먼저 처리할 수 있도록 서블릿컨테이너(톰캣 WAS)에서 지원해 주는 기능.
- Filter들은 여러 개가 연결되어 있어 FilterChain이라고도 불린다.
- 모든 Request들은 FilterChain을 거쳐야지 Dispatcher Servlet에 도착하게 된다.
SecurityFilter
SecurityFilter란
SecurityFilter는 웹 애플리케이션에서 보안과 관련된 작업을 수행하는 필터를 의미합니다. 이 필터들은 클라이언트의 요청과 서버의 응답을 가로채어 보안 기능을 적용하는 중요한 역할을 합니다.
주요 역할에는 다음과 같은 작업이 포함됩니다:
- 인증(Authentication): 사용자의 신원을 확인하는 과정. (예: 아이디/비밀번호 검증, JWT 토큰 검증)
- 권한 부여(Authorization): 인증된 사용자가 특정 리소스에 접근할 수 있는 권한이 있는지 확인.
- 요청 검증(Validation): 요청의 유효성을 검사하여 보안 위협(예: SQL Injection, XSS)을 방지.
- 보안 헤더 설정(Security Headers): HTTP 응답에 보안 헤더를 추가하여 클릭재킹 및 MIME 스니핑 등의 공격 방어.
SecurityFilter는 단독으로 동작하기도 하지만, 일반적으로 여러 개의 필터가 연속적으로 실행되어 보다 강력한 보안 기능을 제공합니다. 이처럼 보안 필터들이 체인 형태로 결합되어 동작하는 구조를 SecurityFilterChain이라고 합니다.
SecurityFilter의 동작 원리
필터는 Servlet Container에서 제공하는 기법입니다. spring security에서는 이 필터를 사용하기 위해 필터를 스프링 빈으로 등록합니다. 하지만 Servlet Container는 스프링 컨테이너에 등록된 빈을 인식할 수 없습니다 그렇기 때문에 Spring Security는 사용하고자 하는 FilterChain들을 Servlet Container 기반의 필터 위에서 동작시키기 위해 DelegatingFilterProxy라는 클래스를 이용합니다. DelegatingFilterProxy는 IOC 컨테이너에서 관리하는 빈이 아닌 표준 서블릿 필터를 구현하고 있으며 DelegatingFilterProxy가 호출이 되면 SecurityFilterChain으로 역할을 위임합니다. DelegatingFilterProxy에 대한 자세한 내용은 따로 정리하도록 하겠습니다.
정해진 순서대로 Spring Security의 필터들이 모두 FilterChain에 엮이게 되고 DelegatingFilterProxy에 의해 SecurityFilterChain 가 호출이 되어 각자 로직을 수행하고 나면 filterChain.doFilter() 메서드를 호출해 다음 필터 클래스의 doFilter() 메서드를 실행시킵니다. ( 필터는 doFilter() 메서드부터 실행된다)
SecurityFilter의 종류
- form 로그인 요청의 경우 필터체인에 들어와서 UsernamePasswordAuthenticationFilter에서 처리됩니다.
코드 살펴보기
시작은 doFilter메서드
- filterChain.doFilter() 메서드로 호출하기 때문에 Filter은 doFiler부터 실행이 된다고 했습니다.
- UsernamePasswordAuthenticationFilter의 doFilter 메서드는 부모클래스인 AbstractAuthenticationProcessingFilter에서 상속받습니다.
1. requiresAuthentication() 메서드를 통한 해당 요청을 처리할 것인지 확인
RequestMatcher를 통해 이 HttpServletRequest가 매칭되는지 확인 후 맞을 경우 try문을 처리하고 아닐 경우 필터체인의 다음 필터로 넘긴다.

이때 인증(로그인) 로직은 UsernamePasswordAuthenticationFilter에 걸란다고 했었는데 아래 코드를 보면
[Post] /login으로 요청이 들어오면 해당 Filter에 걸리도록 설정이 되어있기 때문이다.

결과적으로 로그인 요청은 필터체인을 돌다가 UsernamePasswordAuthenticationFilter에서 걸리게 된다.
2. attemptAuthentication() 호출하여 Authentication 객체를 반환받는다 (인증 로직 실행)
(1) UsernamePasswordAuthenticationFilter.attemptAuthentication()
- AbstractAuthenticationProcessingFilter의 추상메서드인 attemptAuthentication메서드를 구현합니다.
- (1) 사용자가 입력했던 id, password 정보로 UsernamePasswordAuthenticationToken 만들기
- (2) AuthrnticationManager.authentication 메서드로 인증객체의 인증 위임
AuthrnticationManager은 authentication메서드를 제공하는 인터페이스이며 실질적으로 authentication메서드를 처리하는 클래스는 AuthrnticationManager의 구현체인 ProviderManager클래스입니다.
(2) ProviderManager.authenticaticate()
- authentication메서드를 제공하는 AuthrnticationManager의 구현체
(2-1) for문을 돌면서 가지고 있는 providers 중에 해당 인증객체를 처리할 수 있는 provider을 찾음

UsernamePasswordAuthenticationToken을 처리하는 povider은 DaoAuthenticationProvider인 것을 확인할 수 있다
- 각각의 provider들은 supports 메서드를 가지고 있음
- supports 메서드에서 해당 인증객체를 처리할 수 있는지 확인한다.
- DaoAuthenticationProvider의 부모클래스인 AbstractUserDetailsAuthenticationProvider클래스의 supports 메서드

- UsernamePasswordAuthenticationToken을 처리할 수 있는것을 알수있다
(2-2) 위에서 찾은 DaoAuthenticationProvider의 authentication메서드로 인증처리를 위임
- 위에서 DaoAuthenticationProvider가 UsernamePasswordAuthenticationToken을 처리할 수 있다는 것을 확인했다.
- DaoAuthenticationProvider의 authentication메서드는 DaoAuthenticationProvider의 부모클래스인 AbstractUserDetailsAuthenticationProvider에서 상속받는다
(3)DaoAuthenticationProvider.authentication메서드
- AbstractUserDetailsAuthenticationProvider에 구현되어 있는 메서드 상속받음
(3-1) UserDetail정보 받아옴(id검증): DaoAuthenticationProvider의 retrieveUser() 메서드

- UserDetailsService의 loadUserByUsername() 메서드 실행
- UserDetailService에서 username정보로 UserDetail 정보를 가져온다 (id 검증)

- UserDetailsSevice는 loadUserByUsername() 메서드를 제공해 주는 인터페이스
- 보통 사용자가 구현해서 사용한다.
(3-2) 패스워드 검증: DaoAuthenticationProvider의 additionalAuthenticationChecks메서드

- 위에서 받아온 UserDetail의 password정보와 사용자가 입력한 password정보를 비교 검증한다. (password검증)
- 이 때 passwordEncoder을 사용한다.
(3-3) Authentication 객체생성: DaoAuthenticationProvider의 createSuccessAuthentication 메서드
- 여기까지 인증객체에 대한 인증이 정상적으로 완료가 되면 새로운 인증 객체를 만들어서 반환해 준다.

3. successfulAuthentication() 메서드 Authentication 객체를 SecurityContextHolder에 저장
- 인증이 완료된 Authentication객체를 SecurityContextHolder에 저장한다
- 모든 처리가 완료되면 등록된 핸들러를 호출합니다
추가로 알아볼 내용
AbstractAuthenticationProcessingFilter의 doFIlter 메서드에서 Authemtication객체의 인증을 완료 후 successfulAuthentication() 호출하기 전에 세션에 대한 처리? 를 하는 거 같은데 이 코드 부분은 조금 더 공부해 봐야겠습니다.
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
여기까지가 인증에 대한 내용이었고 다음에는 인가에 대한 살펴보겠습니다.