- Published on
Spring Security 5.7.0-M2 부터 변경된 설정 방법
Spring Security 5.7.0-M2 부터 변경된 설정 방법
최종 SecurityConfig
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private static final String DOCS = "/docs/**";
private final CorsConfigurationSource corsConfigurationSource;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final AuthenticationConfiguration authenticationConfiguration;
private final ObjectMapper objectMapper;
private final LoginAuthenticationProvider loginAuthenticationProvider;
private final JWTAuthorizationProvider jwtAuthorizationProvider;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
authenticationManagerBuilder.authenticationProvider(loginAuthenticationProvider);
authenticationManagerBuilder.authenticationProvider(jwtAuthorizationProvider);
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public JWTAuthorizationFilter jwtAuthorizationFilter() throws Exception {
return new JWTAuthorizationFilter(authenticationManager(), objectMapper);
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.antMatchers("/favicon.ico", DOCS);
}
@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
return http
.formLogin()
.disable()
.csrf()
.disable()
.cors()
.configurationSource(corsConfigurationSource)
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests(authz -> {
authz.anyRequest()
.authenticated();
})
.addFilterBefore(jwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.build();
}
}
WebSecurityConfigurerAdapter
기존의 Spring Security는 WebSecurityConfigurerAdapter 클래스를 상속받아 메소드를 Override하여 구현하였습니다.
하지만 WebSecurityConfigurerAdapter가 Deprecated처리 되면서 Bean으로 등록하여 설정하도록 변경되었습니다.
SecurityFilterChain
HttpSecurity 객체에 인가 및 필터들을 등록하는 Bean입니다.
- 기존 방식
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.disable()
.csrf()
.disable()
.cors()
.configurationSource(corsConfigurationSource)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests(authz -> {
authz.antMatchers(API_PUBLIC)
.permitAll()
.anyRequest().authenticated();
})
.addFilterBefore(jwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.build();
}
}
- 변경된 방식
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
return http
.formLogin()
.disable()
.csrf()
.disable()
.cors()
.configurationSource(corsConfigurationSource)
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests(authz -> {
authz.anyRequest()
.authenticated();
})
.addFilterBefore(jwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.build();
}
}
WebSecurityCustomizer
WebSecurity 객체에 설정하기 위한 Bean입니다.
- 기존 방식
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/favicon.ico", DOCS);
}
}
- 변경된 방식
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.antMatchers("/favicon.ico", DOCS);
}
}
AuthenticationManager
AuthenticationManager을 사용하기 위한 방법은 2가지가 있습니다.
UsernamePasswordAuthenticationFilter를 상속받아super.getAuthenticationManager()를 통해 사용AuthenticationManager를Bean으로 등록하여 의존 주입 받아 사용
UsernamePasswordAuthenticationFilter를 상속받아 사용하는 방법은 당연하게도 해당 필터를 상속받지 않으면 사용할수 없다는 단점이 존재합니다.
로그인의 경우 filter에서 구현하기 보다 Controller에서 구현하는 것이 더 다루기가 쉽습니다.
그렇다면 Bean으로 생성해야 하는데, 기존에는 WebSecurityConfigurerAdapter를 상속받았기 때문에, super.authenticationManager()를 Bean으로 등록하여 사용할 수 있었습니다.
이제는 WebSecurityConfigurerAdapter를 상속받지 않기 때문에 다른 방식으로 Bean을 등록해야 합니다.
AuthenticationConfiguration를 통해 생성
AuthenticationManager를 어떻게 생성할지 찾던 도중 공식문서 댓글에서 AuthenticationConfiguration를 통해 생성할 수 있다는 것을 발견했습니다.
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
공식문서가 아니라 댓글에 나와있던 방식으로 좋은 방법은 아닐 수 있습니다.
AuthenticationManager에 AuthenticationProvider 주입
AuthenticationProvider가 하나일 경우 Bean으로 등록만 하면 자동으로 AuthenticationManager에 주입이 됩니다.
하지만, 저는 2개의 AuthenticationProvider가 필요했기 때문에 별도로 주입이 필요했습니다.
SecurityFilterChain에 authenticationProvider를 통해 주입?
SecurityFilterChain에서 authenticationProvider 메소드를 제공합니다.
해당 메소드에 AuthenticationProvider를 주입하면 AuthenticationManagerBuilder 객체의 authenticationProviders필드에 추가 되는것을 확인할 수 있습니다.
이렇게 간단하게 될줄 알았지만 여전히 AuthenticationManager에 등록이 되지 않았습니다.
그래서 디버깅을 해보기로 했습니다.
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
private final LoginAuthenticationProvider loginAuthenticationProvider;
private final JWTAuthorizationProvider jwtAuthorizationProvider;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
return http
.formLogin()
.disable()
.csrf()
.disable()
.cors()
.configurationSource(corsConfigurationSource)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests(authz -> {
authz.antMatchers(API_PUBLIC)
.permitAll()
.anyRequest().authenticated();
})
.authenticationProvider(loginAuthenticationProvider) // 추가
.authenticationProvider(jwtAuthorizationProvider) // 추가
.addFilterBefore(jwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.build();
}
}

디버깅을 돌려보면 AuthenticationConfiguration객체안에 AuthenticationManagerBuilder가 있음을 알 수 있고, AuthenticationManagerBuilder 안의 AuthenticationProviders필드에 DaoAuthenticationProvider가 추가되는 것을 확인할 수 있습니다.

이후, 추가된 AuthenticationProviders와 AuthenticationManager를 가지고 ProviderManager를 생성합니다.

ProviderManager는 추후 AuthenticationManager를 통해 authenticate를 실행 하면 넘어온 Authentication 객체 타입을 지원하는 AuthenticationProvider를 실행합니다.

이어서 진행을 해보면 HttpSecurity객체를 통해 LoginAuthenticationProvider와 JwtAuthenticationProvider가 주입되는 것을 알 수 있습니다.
즉, 이 부분이 SecurityFilterChain를 통해 주입시켰을 때 Provider가 주입되는 과정입니다.

여기서 중요하게 봐야할 점은 WebSecurityConfigurerAdapter객체의 AuthenticationManagerBuilder의 authenticationProviders필드에 주입을 한다는 점입니다.
즉, 2개는 별도의 객체라는 것 입니다.
SecurityFilterChain를 통해 주입한 AuthenticationProvider객체들은 WebSecurityConfigurerAdapter객체에 주입이 되고 Bean으로 생성한 AuthenticationManager은 AuthenticationConfiguration의 AuthenticationManager이기 떄문에 DaoAuthenticationProvider만 존재하는 것입니다.
LoginAuthenticationProvider만 캡쳐하였지만JwtAuthenticationProvider또한 동일하게 생성됩니다.
생각해볼만한 대안들
1. SecurityFilterChain에 주입하고 WebSecurityConfigurerAdapter객체를 통해 AuthenticationManager를 Bean으로 등록하기
이렇게 생성할수도 있겠지만 WebSecurityConfigurerAdapter가 Deprecated당했기 때문에 좋은 방법은 아닌거 같습니다.
2. AuthenticationConfiguration객체를 통해 AuthenticationManager를 Bean으로 등록하기
AuthenticationConfiguration를 통해 AuthenticationManager를 Bean으로 등록하여 사용할려면 AuthenticationProvider를 주입시킬 방법이 필요합니다.

AuthenticationConfiguration객체를 보면 AuthenticationManagerBuilder를 Bean으로 등록한다는 것을 알 수 있습니다.
그렇다면, AuthenticationManagerBuilder를 주입받아 AuthenticationProvider를 추가시켜 주면 됩니다.
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final AuthenticationConfiguration authenticationConfiguration;
private final LoginAuthenticationProvider loginAuthenticationProvider;
private final JWTAuthorizationProvider jwtAuthorizationProvider;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
authenticationManagerBuilder.authenticationProvider(loginAuthenticationProvider);
authenticationManagerBuilder.authenticationProvider(jwtAuthorizationProvider);
return authenticationConfiguration.getAuthenticationManager();
}
}

AuthenticationConfiguration객체의 AuthenticationManagerBuilder에 정상적으로 LoginAuthenticationProvider가 추가되는 것을 확인할 수 있습니다.