Deprecated된 WebSecurityConfigurerAdapter, 어떻게 대처하지?

박진형·2022년 6월 20일
34

Spring

목록 보기
8/9

Deprecated WebSecurityConfigurerAdapter


스프링 버전이 업데이트 됨에 따라 WebSecurityConfigurerAdapter와 그 외 몇 가지들이 Deprecated 됐습니다.
스프링에서는 다른 방식으로 시큐리티 설정을 권장하고 있는 듯 해보였는데요. 방식이 바뀐 탓에 시큐리티 설정에 어려움을 겪었고 제가 사용한 부분에 대해서만 어떻게 변경에 대처할지 기본적인 것만 간단히 알아봅니다.

어떻게 변경되었지?

기존에는 WebSecurityConfigurerAdapter를 상속받아 설정을 오버라이딩 하는 방식이었는데 바뀐 방식에서는 상속받아 오버라이딩하지 않고 모두 Bean으로 등록을 합니다.

변경전 설정과 변경 후 설정

현재 팀프로젝트에서 사용되고 있는 WebSecurityConfigure 파일을 보여드리겠습니다. 전체적으로 어떻게 달라 졌는지 눈으로 간단히 확인한 다음 부분적으로 자세하게 설명을 해볼까 합니다.

코드의 자세한 내용을 보기보다는 전체적인 방식의 차이에 주목하시면 될 것 같습니다. (변경 전은 강의를 보며 실습한 코드고, 변경 후는 팀프로젝트에서 팀프로젝트에 맞게 수정한 것이므로 코드의 내용은 크게 의미 없습니다.)

  • 변경전
package com.prgrms.devcource.configures;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;

import com.prgrms.devcource.configures.jwt.Jwt;
import com.prgrms.devcource.configures.jwt.JwtAuthenticationFilter;
import com.prgrms.devcource.oauth2.OAuth2AuthenticationSuccessHandler;
import com.prgrms.devcource.user.UserService;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {

	private final Logger log = LoggerFactory.getLogger(getClass());

	private final JwtConfigure jwtConfigure;

	private final UserService userService;

	public WebSecurityConfigure(JwtConfigure jwtConfigure, UserService userService) {
		this.jwtConfigure = jwtConfigure;
		this.userService = userService;
	}

	@Override
	public void configure(WebSecurity web) {
		web.ignoring().antMatchers("/assets/**", "/h2-console/**","/api/hello2");
	}

	@Bean
	public AccessDeniedHandler accessDeniedHandler() {
		return (request, response, e) -> {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			Object principal = authentication != null ? authentication.getPrincipal() : null;
			log.warn("{} is denied", principal, e);
			response.setStatus(HttpServletResponse.SC_FORBIDDEN);
			response.setContentType("text/plain;charset=UTF-8");
			response.getWriter().write("ACCESS DENIED");
			response.getWriter().flush();
			response.getWriter().close();
		};
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public Jwt jwt() {
		return new Jwt(
			jwtConfigure.getIssuer(),
			jwtConfigure.getClientSecret(),
			jwtConfigure.getExpirySeconds()
		);
	}

	@Bean
	public OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler () {
		Jwt jwt = getApplicationContext().getBean(Jwt.class);
		return new OAuth2AuthenticationSuccessHandler(jwt, userService);
	}
	

	public JwtAuthenticationFilter jwtAuthenticationFilter() {
		Jwt jwt = getApplicationContext().getBean(Jwt.class);
		return new JwtAuthenticationFilter(jwtConfigure.getHeader(), jwt);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
			.antMatchers("/hello").permitAll()
			.antMatchers("/api/user/me").hasAnyRole("USER", "ADMIN")
			.anyRequest().permitAll()
			.and()
			.formLogin()
			.disable()
			.csrf()
			.disable()
			.headers()
			.disable()
			.httpBasic()
			.disable()
			.rememberMe()
			.disable()
			.logout()
			.disable()
			.sessionManagement()
			.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
			.and()

			.exceptionHandling()
			.accessDeniedHandler(accessDeniedHandler())
				.and()
			.addFilterAfter(jwtAuthenticationFilter(), SecurityContextPersistenceFilter.class)
		;
	}
}
  • 변경 후
package com.kdt.instakyuram.security;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.kdt.instakyuram.security.jwt.Jwt;
import com.kdt.instakyuram.security.jwt.JwtAuthenticationFilter;
import com.kdt.instakyuram.security.jwt.JwtConfigure;
import com.kdt.instakyuram.token.service.TokenService;

@EnableWebSecurity
@Configuration
public class WebSecurityConfigure {
	private final Logger log = LoggerFactory.getLogger(getClass());

	private final JwtConfigure jwtConfigure;

	public WebSecurityConfigure(JwtConfigure jwtConfigure) {
		this.jwtConfigure = jwtConfigure;
	}

	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public Jwt jwt() {
		return new Jwt(
			this.jwtConfigure.issuer(),
			this.jwtConfigure.clientSecret(),
			this.jwtConfigure.accessToken(),
			this.jwtConfigure.refreshToken()
		);
	}

	public JwtAuthenticationFilter jwtAuthenticationFilter(Jwt jwt, TokenService tokenService) {
		return new JwtAuthenticationFilter(
			this.jwtConfigure.accessToken().header(),
			this.jwtConfigure.refreshToken().header(),
			jwt,
			tokenService
		);
	}

	@Bean
	public AccessDeniedHandler accessDeniedHandler() {
		log.warn("accessDeniedHandler");
		return (request, response, e) -> {
			response.setStatus(HttpServletResponse.SC_FORBIDDEN);
			response.setContentType("text/plain;charset=UTF-8");
			response.getWriter().write("ACCESS DENIED");
			response.getWriter().flush();
			response.getWriter().close();
		};
	}

	@Bean
	public AuthenticationEntryPoint authenticationEntryPoint() {
		return (request, response, e) -> {
			response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
			response.setContentType("text/plain;charset=UTF-8");
			response.getWriter().write("UNAUTHORIZED");
			response.getWriter().flush();
			response.getWriter().close();
		};
	}

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http, Jwt jwt, TokenService tokenService) throws
		Exception {
		http
			.authorizeRequests()
			.antMatchers("/api/members/signup", "/api/members/signin").permitAll()
			.anyRequest().authenticated()
			.and()
			.formLogin()
			.disable()
			.csrf()
			.disable()
			.headers()
			.disable()
			.httpBasic()
			.disable()
			.rememberMe()
			.disable()
			.logout()
			.disable()
			.sessionManagement()
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
			.and()
			.exceptionHandling()
			.accessDeniedHandler(accessDeniedHandler())
			.authenticationEntryPoint(authenticationEntryPoint())
			.and()
			.addFilterBefore(jwtAuthenticationFilter(jwt, tokenService), UsernamePasswordAuthenticationFilter.class);

		return http.build();
	}
}

HttpSecurity Configure

  • 간단한 HttpSecurity 설정 예시를 가져 왔습니다. 기존 방식은 아래와 같습니다.
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
			.antMatchers("/hello").permitAll()
			.antMatchers("/api/user/me").hasAnyRole("USER", "ADMIN")
			.anyRequest().permitAll()
			.and()
			.formLogin()
			.disable()
			.csrf()
			.disable()
			.headers()
			.disable()
			.httpBasic()
			.disable()
			.rememberMe()
			.disable()
			.logout()
			.disable()
			.sessionManagement()
			.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
			.and()
			.exceptionHandling()
			.accessDeniedHandler(accessDeniedHandler())
				.and()
			.addFilterAfter(jwtAuthenticationFilter(), SecurityContextPersistenceFilter.class)
		;
	}
  • 바뀐 방식에서는 SecurityFilterChain를 Bean으로 등록하는 방식입니다.
    @Bean
	public SecurityFilterChain filterChain(HttpSecurity http, Jwt jwt, TokenService tokenService) throws
		Exception {
		http
			.authorizeRequests()
			.antMatchers("/api/members/signup", "/api/members/signin").permitAll()
			.anyRequest().authenticated()
			.and()
			.formLogin()
			.disable()
			.csrf()
			.disable()
			.headers()
			.disable()
			.httpBasic()
			.disable()
			.rememberMe()
			.disable()
			.logout()
			.disable()
			.sessionManagement()
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
			.and()
			.exceptionHandling()
			.accessDeniedHandler(accessDeniedHandler())
			.authenticationEntryPoint(authenticationEntryPoint())
			.and()
			.addFilterBefore(jwtAuthenticationFilter(jwt, tokenService), UsernamePasswordAuthenticationFilter.class);

		return http.build();
	}

기존 방식에서는 메서드를 오버라이딩해서 설정을 하고 클래스 내부에 설정 정보를 저장하는 방식인 듯 합니다.

바뀐 방식에서는 모든것들을 Bean으로 등록해서 스프링 컨테이너가 관리할 수 있도록 변경이 된 듯 합니다.

  • 반환 값이 void에서 설정 유형으로 변경되었습니다.
  • 그에 따라 return을 해줘야 되겠죠? (http.build())

getApplicationContext(), getBean()

기존의 방식에서는 WebSecurityConfigurerAdapter가 getApplicationContext() 함수와 getBean() 함수를 제공해줬기 때문에 getApplicationContext().getBean(Jwt.class); 와 같이 Bean을 코드로써 가지고 올 수 있었습니다.

	public JwtAuthenticationFilter jwtAuthenticationFilter() {
		Jwt jwt = getApplicationContext().getBean(Jwt.class);
		return new JwtAuthenticationFilter(jwtConfigure.getHeader(), jwt);
	}

변경 후에는 WebSecurityConfigurerAdapter를 더 이상 사용하지 않기 때문에 Bean을 저런 방식으로 가지고 올 수 없습니다. 대신 Bean을 매개변수로 받으면 자동으로 주입이 되기 때문에 아래와 같은 방식으로 의존성을 주입 시켜 줍니다.

  • SecurityFilterchain 설정이 Bean으로 등록되기 때문에 필요한 Jwt, TokenService와 같은 것들을 매개변수로 받을 수 있습니다.
	
	public JwtAuthenticationFilter jwtAuthenticationFilter(Jwt jwt, TokenService tokenService) {
		return new JwtAuthenticationFilter(
			this.jwtConfigure.accessToken().header(),
			this.jwtConfigure.refreshToken().header(),
			jwt,
			tokenService
		);
	}
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, Jwt jwt, TokenService tokenService) throws
		Exception {
		http
        
        ...중간 생략
        
        .addFilterBefore(jwtAuthenticationFilter(jwt, tokenService), UsernamePasswordAuthenticationFilter.class);

		return http.build();
	}
        

WebSecurity Configure

WebSecurity Configure도 마찬가지 입니다. WebSecurityCustomizer를 Bean으로 등록해서 설정을 하면 됩니다.

  • 변경 전
  @Override
	public void configure(WebSecurity web) {
		web.ignoring().antMatchers("/assets/**", "/h2-console/**","/api/hello2");
	}
  • 변경 후
 @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/assets/**", "/h2-console/**","/api/hello2"));
    }

2개의 댓글

comment-user-thumbnail
2022년 9월 13일

잘 보고 갑니다!

답글 달기
comment-user-thumbnail
2023년 3월 8일

박진형! 박진형! 박진형! 박진형! 박진형! 박진형! 박진형! 박진형! 박진형! 박진형! 박진형! 박진형!

답글 달기