[Spring][Security] 스프링 시큐리티(Spring Security)에 대해서 - 스프링 시큐리티 아키텍처, 필터 - 컴도리돌이
스프링 시큐리티(Spring Seucirty)
스프링 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크. 서블릿 필터와 이들로 구성된 필터 체인으로의 위임 모델을 사용한다. 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안 관련 로직을 작성하지 않아도 된다.
보안 용어
인증(Authenticate) : 접근하려는 유저의 권한을 확인하는 절차 ex) 회원가입, 로그인
인가(Authorization) : 인증된 사용자에 대해서 권한을 확인하고 허락하는 것.
접근 주체(Principal) : 보호된 대상에 접근하는 대상
인증 아키텍처
1. 유저가 폼(form)을 통해 로그인 정보를 입력하고 인증 요청을 보낸다.
AuthenticationFilter
스프링 시큐리는 연결된 필터를 가지고 있다. 모든 요청(Request)은 인증과 인가를 위해서 해당 필터를 통과를 해야 한다.
SeucrityContext에 사용자의 세선 id가 있는지 확인하고 세션 id가 없는 경우 다음 로직을 수행한다.
인증을 성공한 경우 인증된 Authentication 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler를 실행하고, 인증 실패한 경우 AuthenticationFailureHandler를 실행한다.
2. "AuthenticationFilter"가 "HttpServletRequest"에서 사용자가 인증 요청한 아이디와 패스워드를 인터셉터한다.
프런트(Front)에서 유효성 검사를 할 수도 있지만, 안전을 위해서 다시 한번 사용자가 보낸 아이디와 패스워드의 유효성 검사를 한다.
"HttpServletRequest"에서 사용자 아이디와 패스워드를 "AuthenticationManager" 인터페이스에게 인증용 객체(UsernamePasswordAuthenticationToken)로 만들어줘서 위임한다.
UsernamePasswrodAuthenticationToken
인증형 객체를 구현한 AbstractAuthenticationToken의 하위 클래스
인증용 객체(principal, credentials, authorities)를 생성한다.
AuthenticationManager
Authentication을 만들고 인증을 처리하는 인터페이스로, 로그인 시 인자로 받은 인증용 객체를 Provider를 통해 유효한지 처리하여 인증용 객체를 반환해준다.
ProviderManager
AuthenticationManager의 구현체로, 사용자 요청을 인증에 필요한 AuthenticationProvider를 살펴보고 전달된 인증 객체를 기반으로 사용자 인증을 시도한다.
4. 실제 인증을 할 "AuthenticationProvider"에게 인증용 객체를 전달한다.
AuthenticationProvider
실제 인증을 담당하는 인터페이스로, 인증 전 인증용 객체를 받아서 DB에 있는 사용자 정보를 비교하고 인증된 객체를 반환한다.
5. DB에서 사용자 인증 정보를 가져올 "UserDetailsService" 객체에게 사용자 아이디를 넘겨주고 DB에서 인증에 사용할 사용자 정보(아이디, 암호화된 패스워드, 권한 등)를 "UserDetails" 객체로 전달받는다.
UserDetailsService
DB에서 유저 정보를 가져오는 역할을 하며, loadUserByUsername() 메서드를 통해서 DB에서 유저 정보를 가져온다. 커스텀하게 사용하고 싶다면 해당 interface를 implements 받아서 loadUserByUsername() 메서드를 구현하면 된다.
UserDetails
사용자의 정보를 담는 인터페이스이다.
기본적으로 오버라이드 메서드는 다음과 같다.
getAuthorities() : 계정의 권한 목록을 리턴
getPassword() : 계정의 비밀번호 리턴
getUsername() : 계정의 고유한 값 리턴
isAccountNonexpried() : 계정의 만료 여부 리턴
isAccountNonLocked() : 계정의 잠김 여부 리턴
isCredentialsNonExpired() : 비밀번호 만료 여부 리턴
isEnabled() : 계정의 활성화 여부 리턴
6. "AuthenticationProvider"는 "UserDetails" 객체를 전달받은 이후 실제 사용자의 입력 정보와 "UserDetails" 객체를 가지고 인증을 시도한다.
7. 인증이 완료되면 사용자 정보를 가진 "Authentication" 객체를 "SecurityContextHolder"에 담은 이후 "AuthenticationSuccessHandle"를 실행한다.
실패 시 "AuthenticationFailureHandler"를 실행한다.
SecurityContextHolder
SecurityContext를 현재 스레드와 연결시켜주는 역할을 하며, 스프링 시큐리티는 같은 스레드의 애플리케이션 내 어디서든 SecurityContextHolder의 인증 정보를 확인 가능하도록 구현되어 있는데 이 개념을 ThreadLocal이라고 한다.
Security Context
Authentication의 정보를 가지고 있는 인터페이스로 SecurityContextHolder.getContext()를 통해 얻을 수 있다.
Authentication
현재 접근하는 주체의 정보와 권한을 담는 인터페이스로, AuthenticationManager.authenticate(Authentication)에 의해 인증된 principal 혹은 token이다.
스프링 시큐리티 필터(Spring Security Filter)
스프링 시큐리티는 DelegatingFilterProxy 필터를 만들어 메인 Filter Chain에 중간에 넣어, SecurityFilterChain 그룹을 등록한다. 그러면 URL에 따라 적용되는 FIlterChain을 다르게 하는 방법을 사용한다.
WebSecurityConfigurerAdapter는 FilterChain을 구성하는 Configuration 클래스로 해당 클래스의 상속을 통해 FilterChain을 구성할 수 있다. Configure(HttpSecurity http)를 override 하며 필터들을 설정한다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
...
}
}
WebSecurityConfigurerAdapter을 상속받아 Filter Chain을 만드는 Class에 @EnableWebSecurity(debug = true) 어노테이션을 붙여주면 실행되는 Security Filter들을 확인할 수 있다.
1. Filter
서블릿 컨테이너(Servlet Container)의 필터는 디스패치 서블릿(DispatchServlet)으로 가기 전에 먼저 적용된다.
필터들은 여러 개가 연결되어 있어 필터 체인(Filter chain)이라고 불린다.
모든 요청(Request)들은 필터 체인을 거쳐야지 서블릿으로 도착하게 된다.
2. DelegatingFilterProxy
"DelegatingFilterProxy"는 필터 체인을 통해 전달된 웹 요청을 스프링에게 "위임"하는 역할을 한다.
일반적으로 서블릿 컨테이너 자체에 필터를 등록하는 것도 가능하지만, 필터가 스프링 컨테이너에 등록된 빈들을 인식하지 못한다. 그렇기 때문에 스프링은 서블릿 컨테이너와 스프링의 ApplicationContext를 이어주는 다리 역할로 "DelegatingFilterProxy"를 이용한다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
Filter delegate = getFilterBean(someBeanName);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
DelegatingFilterProxy에서는 getFilterBean()을 통해 ApplicationContext을 통해 빈으로 등록된 필터를 가져온다. 그리고 DelegatingFilterProxy가 받은 요청과 응답 객체를 빈으로 등록된 필터의 doFilter()에 넘겨주면서 빈으로 등록된 필터의 작업을 실행시킨다.
3. FilterChainProxy
DelegatingFilterProxy의 내부에는 스프링 시큐리티에 제공하는 FilterChainProxy라는 필터가 존재한다.
FilterChainProxy는 DelegatingFilterProxy를 통해 받은 요청과 응답을 스프링 시큐리티 필터 체인에 전달하고 작업을 위임하는 역할을 한다.
DelegatingFilterProxy에서 바로 SecurityFilterChain을 실행시킬 수 있지만, 중간에 FilterChainProxy를 두는 이유는 서블릿을 지원하는 시작점 역할을 하기 위해서이다. 서블릿에 문제가 발생하면, FilterChainProxy의 문제라는 것을 알 수 있게 된다.
4. SecurityFilterChain
SecurityFilterChain은 인증을 처리하는 여러 개의 시큐리티 필터를 담는 필터 체인이다. FilterChainProxy를 통해 서블릿 필터와 연결되고 시큐리티 필터를 통해 인증을 수행할지 결정하는 역할을 한다.
여러 개의 SecurityFilterChain을 구성하여 매칭 되는 URL에 따라 다른 SecurityFilterChain이 사용되도록 할 수 있다.
5. Security Filters
SecurityFilters는 요청을 스프링 시큐리티 메커니즘에 따라 처리하는 필터이다.
DelegatingFilterProxy와 FilterChainProxy가 서블릿과 스프링과의 연결을 담당했다면, SecurityFilter는 시큐리티의 핵심 기능을 수행하는 지점이다.
SecurityFilter는 SecurityFilterChain API를 통해 FilterChainProxy에 삽입되고 스프링 빈으로 등록된다.
해당 링크에서는 Spring Security에 존재하는 필터들의 목록을 확인할 수 있는데, 해당 페이지에 나열된 필터들은 웹 요청이 통과하는 순서대로 나열되어 있다. 즉 가장 위에 있는 ChannelProcessingFilter가 가장 먼저 동작한다.