토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

번역을 완료하지 않은 문서입니다. 언제든지 내용을 수정할 수 있습니다.

스프링 시큐리티 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

목차:


스프링 프레임워크의 전반적인 자바 설정 지원은 스프링 3.1에서 추가됐다. 스프링 시큐리티는 3.2 버전부터 자바 설정을 지원하므로 XML 없이도 손쉽게 스프링 시큐리티 설정을 만들할 수 있다.

시큐리티 네임스페이스 설정에 익숙하다면, 자바 설정도 네임스페이스 설정과 꽤 유사하다는 걸 알 수 있을 거다.

스프링 시큐리티는 자바 설정을 사용하는 많은 샘플 어플리케이션을 제공한다.


16.1. Hello Web Security Java Configuration

먼저 스프링 시큐리티 자바 설정을 만드는 것부터 시작한다. 이 설정은 springSecurityFilterChain으로 알려진, 어플리케이션 내 모든 보안 처리를 (어플리케이션 URL 보호, 제출한 사용자 이름과 비밀번호 검증, 로그인 폼으로 리다이렉트 등) 담당하는 서블릿 필터를 생성한다. 다음은 가장 기본적인 스프링 시큐리티 자바 설정 예시다:

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;

@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
        return manager;
    }
}

이 설정은 간단하지만 많은 일을 해준다. 기능을 요약하면 다음과 같다:

16.1.1. AbstractSecurityWebApplicationInitializer

다음 단계는 war에 springSecurityFilterChain을 등록하는 것이다. 자바 설정에선 서블릿 3.0+ 환경에서 지원하는 스프링의 WebApplicationInitializer를 사용할 수 있다. 놀랄것도 없이 당연히 스프링 시큐리티는 springSecurityFilterChain을 등록해주는 베이스 클래스 AbstractSecurityWebApplicationInitializer를 제공한다. AbstractSecurityWebApplicationInitializer를 사용하는 방법은 이미 스프링을 사용하고 있는지 또는 스프링 시큐리티가 어플리케이션의 유일한 스프링 컴포넌트인지에 따라 다르다.

16.1.2. AbstractSecurityWebApplicationInitializer without Existing Spring

스프링이나 스프링 MVC를 사용하고 있지 않다면, WebSecurityConfig를 사용할 수 있도록 이 클래스를 상위 클래스로 넘겨야 한다. 예시는 아래에 있다:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(WebSecurityConfig.class);
    }
}

SecurityWebApplicationInitializer는 다음과 같은 일을 한다:

16.1.3. AbstractSecurityWebApplicationInitializer with Spring MVC

어플리케이션의 다른 곳에서 스프링을 사용하고 있다면 아마 스프링 설정을 로딩하는 WebApplicationInitializer가 이미 있을 거다. 이전 설정을 그대로 사용하면 오류가 생긴다. 대신에 기존 ApplicationContext에 스프링 시큐리티를 등록해야 한다. 예를 들어 스프링 MVC를 사용하고 있다면 SecurityWebApplicationInitializer는 다음과 같을 것이다:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

}

이 코드는 단순히 모든 어플리케이션 URL에 springSecurityFilterChain 필터를 등록한다. 이렇게 하면 기존 ApplicationInitializer에 WebSecurityConfig를 로드한다. 예를 들어 스프링 MVC를 사용하고 있다면 getRootConfigClasses() 안에 추가된다.

public class MvcWebApplicationInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { WebSecurityConfig.class };
    }

    // ... other overrides ...
}

16.2. HttpSecurity

지금까지 WebSecurityConfig에는 사용자를 인증하는 방법에 대한 정보만 있었다. 스프링 시큐리티는 모든 사용자를 인증해야 한다는 걸 어떻게 알 수 있을까? 폼 기반 인증을 지원해야 한다는 것은 또 어떻게 알까? 사실은 뒷단에서 실행하는 WebSecurityConfigurerAdapter라는 설정 클래스가 있다. 이 클래스는 디폴트로 아래와 같이 구현돼 있는 configure라는 메소드가 있다:

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorize -> authorize
            .anyRequest().authenticated()
        )
        .formLogin(withDefaults())
        .httpBasic(withDefaults());
}

이 디폴트 설정은:

이 설정은 XML 네임스페이스 설정과도 매우 유사하다는 것을 알 수 있다:

<http>
    <intercept-url pattern="/**" access="authenticated"/>
    <form-login />
    <http-basic />
</http>

16.3. Multiple HttpSecurity

<http> 블록을 여러 개 만들 수 있듯이, HttpSecurity 인스턴스도 여러 개 설정할 수 있다. 핵심은 WebSecurityConfigurerAdapter를 여러 번 상속하는 것이다. 예를 들어 다음 예제에선 /api/로 시작하는 URL은 다른 설정을 사용한다:

@EnableWebSecurity
public class MultiHttpSecurityConfig {
    @Bean                                                           // (1)
    public UserDetailsService userDetailsService() throws Exception {
        // ensure the passwords are encoded properly
        UserBuilder users = User.withDefaultPasswordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("user").password("password").roles("USER").build());
        manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
        return manager;
    }

    @Configuration
    @Order(1)                                                        // (2)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/api/**")                               // (3)
                .authorizeRequests(authorize -> authorize
                    .anyRequest().hasRole("ADMIN")
                )
                .httpBasic(withDefaults());
        }
    }

    @Configuration                                                   // (4)
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests(authorize -> authorize
                    .anyRequest().authenticated()
                )
                .formLogin(withDefaults());
        }
    }
}

(1) 평소처럼 인증을 설정한다.
(2) WebSecurityConfigurerAdapter 인스턴스를 생성하고, @Order를 명시해서 가장 우선시할 WebSecurityConfigurerAdapter로 설정한다.
(3) http.antMatcher는 이 HttpSecurity/api/로 시작하는 URL에만 적용한다고 말하고 있다.
(4) 다른 WebSecurityConfigurerAdapter 인스턴스를 생성한다. /api/로 시작하지 않는 URL은 이 설정을 사용한다. 이 설정은 1보다 큰 @Order 값을 가지므로 ApiWebSecurityConfigurationAdapter 이후에 사용한다 (기본적으로 @Order를 생략하면 마지막이 된다).


16.4. Custom DSLs

스프링 시큐리티에 직접 만든 커스텀 DSL을 제공할 수도 있다. 예를 들어 다음과 같은 코드가 있을 수 있다:

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
    private boolean flag;

    @Override
    public void init(H http) throws Exception {
        // any method that adds another configurer
        // must be done in the init method
        http.csrf().disable();
    }

    @Override
    public void configure(H http) throws Exception {
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);

        // here we lookup from the ApplicationContext. You can also just create a new instance.
        MyFilter myFilter = context.getBean(MyFilter.class);
        myFilter.setFlag(flag);
        http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
    }

    public MyCustomDsl flag(boolean value) {
        this.flag = value;
        return this;
    }

    public static MyCustomDsl customDsl() {
        return new MyCustomDsl();
    }
}

실제로 HttpSecurity.authorizeRequests() 같은 메소드를 구현하는 방식과 동일하다.

이 커스텀 DSL은 다음과 같이 사용할 수 있다:

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(customDsl())
                .flag(true)
                .and()
            ...;
    }
}

코드 실행 순서는 다음과 같다:

원한다면 SpringFactories를 사용해서, WebSecurityConfiguerAdapter가 기본적으로 MyCustomDsl을 추가하도록 만들 수 있다. 예를 들어 아래와 같은 내용을 담은 META-INF/spring.factories 리소스를 클래스패스에 만들 수 있다:

META-INF/spring.factories

org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl

디폴트 설정을 명시적으로 비활성화할 수도 있다.

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(customDsl()).disable()
            ...;
    }
}

16.5. Post Processing Configured Objects

스프링 시큐리티의 자바 설정은, 설정하는 모든 객체의 전체 프로퍼티를 노출하지는 않는다. 이로써 대다수에게는 설정을 단순화해준다. 어쨋든, 모든 프로퍼티를 노출했다면 사용자는 표준 빈 설정을 사용했을 것이다.

모든 프로퍼티를 직접 노출하지 않는 데에는 그만한 이유가 있지만, 좀 더 세세하게 설정을 변경하고 싶을 수도 있다. 이를 해결하기 위해 스프링 시큐리티는 자바 설정으로 만든 인스턴스를 수정하거나 대체할 수 있는 ObjectPostProcessor 개념을 도입했다. 예를 들어 FilterSecurityInterceptorfilterSecurityPublishAuthorizationSuccess 프로퍼티를 설정하려는 경우 다음 코드를 사용할 수 있다:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorize -> authorize
            .anyRequest().authenticated()
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                public <O extends FilterSecurityInterceptor> O postProcess(
                        O fsi) {
                    fsi.setPublishAuthorizationSuccess(true);
                    return fsi;
                }
            })
        );
}

전체 목차는 여기에 있습니다.

<< >>