Authentication and Access Control
애플리케이션에서 보안은 인증과 권한의 두 가지 문제로 요약된다. 스프링 시큐리티는 인증과 권한을 분리하도록 설계되어있으며, 두 가지 모두 각각의 전략과 확장 지점들을 가지고 있다.
Authentication
Authentication의 주요 인터페이스는 AuthenticationManager이며, 이는 딱 한 개의 메소드를 가지고 있다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
이 메소드가 하는 일은 다음 3가지이다.
- 입력이 유요한 주체인 경우 Authentication을 반환한다. (보통 authenticated = true와 함께)
- 입력이 유효하지 않은 경우 AuthenticationException 예외를 던진다.
- 만약에 유효한지 결정 할 수 없다면 null을 리턴한다.
ProviderManager
AuthenticationManager로 가장 흔하게 사용되는 구현체가 ProviderManager이며, 이는 AuthenticationProvider 리스트의 provider를 실행하고, 다음 provider가 실행할 수 있도록 도와주는 역할을 한다. AuthenticationProvider는 AuthenticationManager와 비슷한데, 호출자가 지정된 인증 유형을 지원하는지 여부를 확인할 수 있는 추가 메서드가 있다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
providerManager는 부모를 가질 수 있는데, 만약 모든 providers들이 null을 리턴하면 이 대 부모를 참조하여 일을 처리할 수 있다. 각각의 ProviderManager들은 같은 ProviderManager를 둘 수 있으며, 이 때 부모 ProviderManager는 모든 provider들을 관리한다.
Customizing Authentication Managers
스프링 시큐리티는 인증에 필요한 정보를 신속하게 얻을 수 있는 몇 가지 방법을 제공한다. 가장 흔하게 사용되는 것이 AuthenticationManagerBuilder이며, JDBC / LDAP / in-memory / Custom UserDetailService 등을 추가하고 설정하는데 큰 도움을 준다.
다음 코드는 parent AuthenticationManager의 예제 코드이다.
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
... // web stuff here
@Autowired
public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
만약에 @Override 메서드를 설정자 파일에 사용했다면, AuthenticationManagerBuilder는 오직 local authenticationManager를 만드는데 사용된다. 스프링 부트에서 global authenticationManager를 다른 빈에 주입시킬 수 있지만, local authenticationManager에는 불가능하다.
스프링 부트는 기본적으로 사용자 한 명만 포함한 Global AuthenticationManager를 제공한다.
Authorization or Access Control
일단 인증에 성공하면, 권한 허가로 넘어갈 수 있는데 권한 허가의 핵심 역할을 하는 것이 AccessDecisionManager이다. 여기에는 3개의 구현체가 있으며 모두 AccessDecisionVoter 객체들을 실행한다.
Web Security
스프링 시큐리티는 서블릿 Filter에 기반하여 동작한다. 다음은 싱글 요청이 왔을 때의 전형적인 핸들러의 계층도이다.
- 클라이언트의 요청이 들어오면 컨테이너는 어떤 필터와 서블릿이 실행될 지 요청 URI를 근거로 판단한다. 보통 1개의 Servlet은 1개의 요청을 처리하기 때문에 필터 체인의 필터들은 순서대로 실행된다. (중간에 필터는 다음 필터의 실행을 거부 할 수 있다.)
- 스프링 부트는 이를 2가지 메카니즘으로 관리한다. Filter 타입의 @Beans는 @Order 또는 Ordered implement를 할 수 있으며, FilterRegistrationBean의 일부가 될 수 있음.
- 스프링 시큐리티는 싱글 Filter로서 체인에 생성되고 구현체는 FilterChainProxy이다. 스프링 부트에서는 시큐리 필터는 ApplicationContext에 있는 @Bean이며, 모든 요청에 적용될 수 있도록 기본적으로 셋팅된다.
- 스프링 시큐리티는 싱글 Filter이지만, 내부 필터안에 여러가지 필터들을 실행시킬 수 있도록 되어 있다.
- FilterChainProxy는 모든 보안 로직을 내부적으로 체인 필터로 가지고 있으며, 모든 필터들은 같은 API 인터페이스를 가지고 있다.
- 필터의 실행은 한 번에 하나씩만 수행될 수 있고, 만약 현재 실행중인 필터가 다음 필터를 실행시키고 싶지 않다면 실행시키지 않고 종료시킬 수 있다.
Request Matching for Dispatch and Authorization
시큐리티 필터 체인(WebSecurityConfigurerAdapter)는 HTTP요청이 들어오면 수 많은 필터 중에서 어떤 필터를 선택할지 판단하고 실행 요청을 하는 matcher를 가지고 있다.
권한 별로 더 구체적인 설정은 HttpSecurity 설정에서 추가적인 매처를 설정함으로써 할 수 있다.
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/match1/**")
.authorizeRequests()
.antMatchers("/match1/user").hasRole("USER")
.antMatchers("/match1/spam").hasRole("SPAM")
.anyRequest().isAuthenticated();
}
}