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 SecurityWebSecurityConfigurerAdapter 클래스를 상속받아 메소드를 Override하여 구현하였습니다.

하지만 WebSecurityConfigurerAdapterDeprecated처리 되면서 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()를 통해 사용
  • AuthenticationManagerBean으로 등록하여 의존 주입 받아 사용

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();
	}
}

HttpSecurity authenticationProvider 디버깅 포인트 AuthenticationManagerBuilder authenticationProvider 디버깅 포인트

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

AuthenticationConfiguration의 AuthenticationManagerBuilder에 DaoAuthenticationProvider주입

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

ProviderManager 생성

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

ProviderManager authenticate

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

HttpSecurity객체를 통해 AuthenticationManagerBuilder에 LoginAuthenticationProvider주입 WebSecurityConfigurerAdapter의 AuthenticationManagerBuilder에 LoginAuthenticationProvider주입

여기서 중요하게 봐야할 점은 WebSecurityConfigurerAdapter객체의 AuthenticationManagerBuilderauthenticationProviders필드에 주입을 한다는 점입니다.

즉, 2개는 별도의 객체라는 것 입니다.

SecurityFilterChain를 통해 주입한 AuthenticationProvider객체들은 WebSecurityConfigurerAdapter객체에 주입이 되고 Bean으로 생성한 AuthenticationManagerAuthenticationConfigurationAuthenticationManager이기 떄문에 DaoAuthenticationProvider만 존재하는 것입니다.

LoginAuthenticationProvider만 캡쳐하였지만 JwtAuthenticationProvider또한 동일하게 생성됩니다.

생각해볼만한 대안들

1. SecurityFilterChain에 주입하고 WebSecurityConfigurerAdapter객체를 통해 AuthenticationManager를 Bean으로 등록하기

이렇게 생성할수도 있겠지만 WebSecurityConfigurerAdapterDeprecated당했기 때문에 좋은 방법은 아닌거 같습니다.

2. AuthenticationConfiguration객체를 통해 AuthenticationManager를 Bean으로 등록하기

AuthenticationConfiguration를 통해 AuthenticationManagerBean으로 등록하여 사용할려면 AuthenticationProvider를 주입시킬 방법이 필요합니다.

AuthenticationConfiguration에서 AuthenticationManagerBuilder Bean 등록

AuthenticationConfiguration객체를 보면 AuthenticationManagerBuilderBean으로 등록한다는 것을 알 수 있습니다.

그렇다면, 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에 AuthenticationProvider 정상 등록

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

참고 사이트