일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- #스파르타내일배움캠프TIL
- Token
- 생성자
- 감사기록
- 스파르타내일배움캠프TIL
- 메서드
- Github_token
- 내일배움캠프
- 회고록
- #내일배움캠프
- KPT
- 해우소
- Java의 이점
- 스레드
- JVM
- TiL_1st_0419
- GitHub
- 스파르타내일배움캠프
- 인스턴스
- diary
- Git
- Java
- 포맷은 최후의 보루
- 객체지향 언어
- #스파르타내일배움캠프
- 성장기록
- Diary 해우소
- 변수의 다양성
- 클래스
- static
- Today
- Total
몬그로이
Security 인증 절차 풀어보기 본문
기본적인 Filter 안에 Delegating Filter Proxy 라는 서블릿 필터가 있다
Security에서 제공하는 Filter들을 실행시키는 클래스는 Filter Chain Proxy인데,
이 Filter Chain Proxy를 Delegating Filter Proxy가 실행 시킨다
*Security Filter 들에 커스텀한 Filter를 넣을 수도 있다
즉, 로그인을 요청하면 필터들이 제일 먼저 반응하는데, 그 중에 Delegating Filter Proxy라는 것이 있어서
그 필터가 작동되면서 Filter Chain Proxy를 실행시키고,
이 Filter Chain Proxy가 Security 필터들을 작동시키는 것
Spring Security는 기본적으로 세션 쿠키 방식을 사용해서 인증 절차를 수행하는데,
그 인증을 맡는 Filter가 UsernamePasswordAuthenticationFilter 이다
이 필터는 AbstractAuthenticationProcessingFilter를 상속받고 있다
AbstractAuthenticationProcessingFilter 에는 doFilter() 와 attemptAuthentication() 메서드가 있으며
doFilter()는 일반 메서드, attemptAuthentication() 는 추상메서드 이다
따라서 UsernamePasswordAuthenticationFilter 는 attemptAuthentication()를 구현해 놓았고
doFilter()는 FilterChainProxy에서 Filter 목록을 호출할때 사용한다
즉, Delegating Filter Proxy 가 Security 차례인 것을 Filter Chain Proxy 에게 알려주면
Filter Chain Proxy 가 doFilter() 메서드를 실행하여 Filter 목록을 호출하게 되는 것
doFilter() 중간에 해당 클래스에서 가지고 있는 추상메서드 attemptAuthentication()가 실행되는데,
이 때 UsernamePasswordAuthenticationFilter 의 attemptAuthentication()가 실행된다
UsernamePasswordAuthenticationFilter 에 구현된 attemptAuthentication() 메서드는
로그인시 입력된 request에서 username 과 password를 추출하여 둘 다 null이 아닌 경우
UsernamePasswordAuthenticationToken 타입을 authRequest 라는 이름으로 생성한다
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password);
그 후, setDetails(request, authRequest); 를 통해 Details를 설정하고
마지막에 this.getAuthenticationManager().authenticate(authRequest);를 반환한다
즉, attemptAuthentication()를 실행시키면 AuthenticationManager를 호출하여 authenticate() 메서드를 실행하는 것
UsernamePasswordAuthenticationToken 는 AbstractAuthenticationToken을 상속받는데,
AbstractAuthenticationToken 클래스는 Authentication을 상속받는다
*AbstractAuthenticationToken 필드로는 details, authenticatied=false, authorities 가 있다
즉, 생성된 UsernamePasswordAuthenticationToken 이
SecurityContextHolder의 SecurityContext에 담기는
그 Authentication인 것이다 (물론 인증이 끝난 후에 담김)
UsernamePasswordAuthenticationToken 클래스는 principal 과 credentials를 필드로 가지고 있고,
이 필드들이 매개변수로 들어오면 생성할 수 있는 생성자가 두 가지가 있다
하나는 (Object principal, Object credentials) 만 받아서 생성되는 대신
super(null); 과 setAuthenticated(false); 로 설정을 하고
다른 하나는 (Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)를 받아서
super(authorities); 과 super.setAuthenticated(true); 로 설정을 한다
즉, 앞서 살펴봤던 attemptAuthentication() 메서드에서 생성하던 authRequest는
첫 번째 생성자를 사용한 것으로, 아직 인증되지 않은 Authentication 객체인 것임을 알 수 있다
이제 세 칸 위에서 호출됐던 AuthenticationManager 를 알아보자
getAuthenticationManager() 메서드는
UsernamePasswordAuthenticationFilter 가 상속받고 있는 AbstractAuthenticationProcessingFilter 의 객체인 AuthenticationManager 를 불러내는 메서드이다
private AuthenticationManager authenticationManager;
interface 인 AuthenticationManager 클래스는 AuthenticationProvider라는 클래스 객체를 관리한다
*AuthenticationProvider는 실제 인증 로직이 담긴 객체이다
메서드는 authenticate(Authentication authentication)하나만 가지고 있으며
이 메서드의 반환타입은 Authentication이라고 정의만 되어있다
즉, UsernamePasswordAuthenticationFilter 가 AuthenticationManager의 authenticate() 메서드를 발동시키면
AuthenticationManager 의 구현체인 ProviderManager 에 있는 authenticate()가 실행된다
그리고 그 결과로 Authentication객체를 반환한다
ProviderManager 의 authenticate() 메서드에는
AuthenticationProvider들을 for문으로 돌리고 있다
인터페이스 AuthenticationProvider에 authenticate() 메서드가 정의(만) 되어 있는 것 뿐 아니라
supports() 라는 메서드도 정의(만) 되어있다
supports()는 ProviderManager 의 authenticate()메서드의 if(!provider.supports()) 에서 사용되고 있는데,
이 if문은 매개변수로 (Class<? extends Authentication> 타입) 을 받는다
이는 authenticate()의 매개변수로 들어온 Authentication 객체가
AuthenticationProvider 객체에서 사용하는 Authentication과 같은지를 확인하는 절차이다
*if문이 extends Authentication 타입을 매개변수로 받고 있어서
이것이 참인 경우 다음 매서드로 넘어가고, 그렇지 않을 경우 다음 AuthenticationProvider로 for문이 돌게 되기 때문이다
즉, AuthenticationProvider 마다 다른 Authentication을 가지고 있어서
authenticate() 매서드의 매개변수로 들어온 authentication과 맞는 객체를 가진 AuthenticationProvider 를 찾는 것이다
위에서 찾은 인터페이스 AuthenticationProvider 의 구현체 또한 각각 authenticate() 메서드가 정의되어 있고,
이 메서드가 ProviderManager 의 for문이 끝난 다음에 실행되는 메서드이다
AuthenticationProvider 의 구현체의 예로 AbstractDetailsAuthenticationProvider 클래스가 있다
이 클래스가 바로 우리가 인증을 위해서 사용할 Authentication을 가지고 있는 클래스이다
AbstractDetailsAuthenticationProvider 의 authenticate() 메서드를 보면
매개변수로 들어온 Authentication에서 username을 추출한다
username 이 null이 아닐 경우, 캐시에서 찾아서 UserDetails 타입으로 건져내고
캐시에서 찾은 이 UserDetails가 DB에 동일한 것이 있는지 체크한다(검증)
만약 캐시가 발견되지 않으면 DB에 있던 UserDetails로 앞서 캐시에서 찾았던 UserDetails를 대체하여 앞선 내용을 똑같이 수행한다.
이때 대체하는 메서드로 retrieveUser() 메서드가 수행되는데,
AbstractDetailsAuthenticationProvider 의 retrieveUser() 메서드는 추상메서드이므로
AbstractDetailsAuthenticationProvider 를 상속받은 DaoAuthenticationProvider 클래스에서 구현해 둔 retrieveUser() 메서드가 수행된다
" 캐시에서 찾은 이 UserDetails가 DB에 동일한 것이 있는지 체크한다(검증) "는 부분을 수행하는 메서드는
additionalAuthenticationChecks() 인데, 이 또한 DaoAuthenticationProvider 에 구현되어 있다
authenticate() 메서드의 결과로 createSuccessAuthentication(principalToReturn, authentication, user) 가 반환된다
*여기서 매개변수 user는 UserDetails 타입임
createSuccessAuthentication() 메서드는
UsernamePasswordAuthenticationToken 타입의 결과를 반환한다
6칸 앞에서 UsernamePasswordAuthenticationToken은 생성자가 두 가지가 있다고 했는데,
이 중에서 두 번째인 Authorities까지 포함한 Token 이 반환된다
*user 에서 getAuthorities()로 추출한 Authorities가 들어가게 됨
이렇게 doFilter() 의 attemptAuthentication()가 실행된 결과로 UsernamePasswordAuthenticationToken 타입의 authResult가 만들어졌다
이후 doFilter()의 반환값은
successfulAuthentication(request, response, chain, authResult) 가 되며
만약 authResult가 만들어지지 못하면 (또는 예외가 발생하면)
unsuccessfulAuthentication(request, response, failed) 가 반환된다
successfulAuthentication() 메서드에는
authResult를 SecurityContext에 저장하는 코드가 들어있어서
이것으로 인증 절차가 끝나게 된다
참고
https://cjw-awdsd.tistory.com/45
https://jaykaybaek.tistory.com/27
추후 추가하여 정리하면 좋을 포스트