스프링 버전이 업데이트 됨에 따라 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();
}
}
@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)
;
}
@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으로 등록해서 스프링 컨테이너가 관리할 수 있도록 변경이 된 듯 합니다.
기존의 방식에서는 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을 매개변수로 받으면 자동으로 주입이 되기 때문에 아래와 같은 방식으로 의존성을 주입 시켜 줍니다.
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도 마찬가지 입니다. 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"));
}
잘 보고 갑니다!