인프런/스프링 시큐리티 완전 정복 [6.x 개정판]

2) 초기화 과정이해 - WebSecurity / HttpSecurity

backend dev 2024. 10. 2.

HttpSecurity

HttpSecurityConfiguration에서 HttpSecurity를 생성하고 초기화를 진행한다

 

HttpSecurity는 보안에 필요한 각 설정 클래스와 필터들을 생성하고 최종적으로 SecurityFilterChain 빈 생성

 

HttpSecurityConfiguration를 통해 HttpSecurity를 수동빈 등록하는데 빈을 생성할때 설정을 진행하며

Configurer가 만들어지고 최종적으로 HttpSecurity가 생성되면 Configurer안의 init, configure 메소드를 통해 필터들이 생성되고 등록된다.

 

 

SecurityFilterChain

 

 

- SecurityFilterChain는 하나가 아니라 여러개가 생성될 수 있다.

 

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
    private final RequestMatcher requestMatcher;
    private final List<Filter> filters;

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
        this(requestMatcher, Arrays.asList(filters));
    }

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
        if (filters.isEmpty()) {
            logger.debug(LogMessage.format("Will not secure %s", requestMatcher));
        } else {
            List<String> filterNames = new ArrayList();
            Iterator var4 = filters.iterator();

            while(var4.hasNext()) {
                Filter filter = (Filter)var4.next();
                filterNames.add(filter.getClass().getSimpleName());
            }

            String names = StringUtils.collectionToDelimitedString(filterNames, ", ");
            logger.debug(LogMessage.format("Will secure %s with filters: %s", requestMatcher, names));
        }

        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList(filters);
    }

    public RequestMatcher getRequestMatcher() {
        return this.requestMatcher;
    }

    public List<Filter> getFilters() {
        return this.filters;
    }

    public boolean matches(HttpServletRequest request) {
        return this.requestMatcher.matches(request);
    }

    public String toString() {
        String var10000 = this.getClass().getSimpleName();
        return var10000 + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters + "]";
    }
}

RequestMatcher를 통해 해당 요청에 맞는 필터인지 체크 한다.

개별 필터에 RequestMatcher를 생성해서 쓰는경우도 있지만

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll()
                .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN")
                .requestMatchers(new AntPathRequestMatcher("/user/**")).hasRole("USER")
                .requestMatchers(HttpMethod.POST, "/api/**").authenticated()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
            .and()
            .logout()
                .logoutSuccessUrl("/")
                .permitAll()
            .and()
            .csrf().disable();  // Note: Disabling CSRF is not recommended for production
    }

다음과 같이 HttpSecurity 설정을 통해 공통으로 지켜야할 규칙에 대한 RequestMatcher 설정을 진행한다.

request가 오면 requestMatcher를 통해 필터가 동작해야하는건지 확인

 

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()  // 여기서부터는 공유되는 RequestMatcher 설정
                .requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll()
                .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
            .and()
            .logout()  // 로그아웃에 대한 특정 RequestMatcher 설정
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))  // 특정 필터에 대한 개별 설정
                .logoutSuccessUrl("/login?logout")
                .permitAll();
    }
}

이렇게 특정 filter에 대해 적용되는 RequestMatcher도 설정가능하다.

 

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AuthenticatedExcelDownloadFilter extends OncePerRequestFilter {

    private final AntPathRequestMatcher requestMatcher;

    public AuthenticatedExcelDownloadFilter(String urlPattern) {
        this.requestMatcher = new AntPathRequestMatcher(urlPattern);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 
            throws ServletException, IOException {
        if (requestMatcher.matches(request)) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null && auth.isAuthenticated() && auth.getAuthorities().stream()
                    .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
                // 엑셀 다운로드 로직 구현
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.setHeader("Content-Disposition", "attachment; filename=download.xlsx");
                // 여기에 실제 엑셀 생성 및 다운로드 로직 추가
                // ...
                return; // 필터 체인 진행을 여기서 중단
            }
        }
        filterChain.doFilter(request, response);
    }
}

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/excel/download/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
            .and()
            .addFilterAfter(new AuthenticatedExcelDownloadFilter("/excel/download/**"), UsernamePasswordAuthenticationFilter.class);
    }
}

이런식으로 특정 필터에 적용할 requestmatcher 만들고 HttpSecurity 설정에서 addFilterAfter() 또는 addFilterBefore()을 이용해서 필터를 추가해줄 수 있다.

 

 

WebSecurity

WebSecurityConfiguration에서 WebSecurity를 생성하고 초기화를 진행한다

WebSecurityHttpSecurity에서 생성한 SecurityFilterChain빈을 SecurityBuilder에 저장한다

WebSecurity build() 를 실행하면 SecurityBuilder 에서 SecurityFilterChain 을 꺼내어 FilterChainProxy 생성자에게 전달한다

[WebSecurity 가 build()를 실행하면 FilterChainProxy가 생성된다. ]

WebSecurity의 performBuild() 일부분

 

최종적으로 FilterChainProxy가 생성되고, 그 Proxy는 설정한 모든 보안 필터목록을 다 가지고 있는다. 

 

 

댓글