잊지 않겠습니다.

'Configuration'에 해당되는 글 1건

  1. 2013.12.02 Code Base Spring Security 기본

Spring 3.0에서 처음 도입된 Code Base Configuration은 이제 Spring 설정의 대세가 되어가는 기분이 든다. 

그런데, 이상하게도 Spring Security의 경우에는 Code Base Configuration이 적용되는 것이 매우 늦어지고 있다는 생각이 든다. 최신 3.2.0RC2에서의 Code Base Security를 구현하는 방법에 대해서 간단히 알아보기로 한다. 


1. @EnableWebSecurity Annotation

Spring Security를 사용하는 @Configuration은 @EnableWebSecurity annotation을 반드시 가져야지 된다.


2. WebSecurityConfigurerAdapter의 상속

@Configuration을 구성한 객체는 기본적으로 WebSecurityConfigurerAdapter를 상속받아야지 된다. 이 상속은 기본적인 method의 stub을 제공한다. 


@EnableWebSecurity
@Configuration
@Slf4j
@ComponentScan("co.kr.daesung.app.center.api.web.cors")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    public static final String CORS_SUPPORT_FILTER = "corsSupportFilter";
    public static final String ADMIN_ROLE = "ADMIN";

3. configurer 구현

configurer method를 override시켜서, configuration을 구성한다. 기본적으로 configuration xml에서 구성된 내용들은 configurer method에서 구현된다. 


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                    .loginPage("/login")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .defaultSuccessUrl("/main")
                    .failureUrl("/login?error=loginFailed")
                .and()
                .authorizeRequests()
                    .antMatchers("/api/admin/**").hasRole(ADMIN_ROLE)
                    .antMatchers("/api/apiKey/**").authenticated()
                    .antMatchers("/api/message/**").authenticated()
                    .antMatchers("/api/auth/**").authenticated();
    }

기본적으로 구성할 수 있는 login form이다. /login에서 username, password를 이용해서 로그인 하게 되며, login success와 failure를 모두 처리하도록 되어있다. 

4. AuthenticationProvider의 구성

AuthenticationProvider는 UserDetailsService와 PasswordEncoder를 반드시 구성해준다. AuthenticationProvider는 인증을 위한 사용자 Pool과 username, password간의 matching을 하는 방법을 제공한다.  주의점은 UserDetailsService이다. 반드시 BeanIds.USER_DETAILS_SERVICE bean name으로 등록이 되어야지 된다. Spring 내부에서 이 Bean Name을 이용해서 UserDetailsService를 구성하기 때문이다.


    /**
     * UserDetailService와 PasswordEncoder를 이용해서, AuthenticationProvider를 구성한다.
     * @return
     * @throws Exception
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() throws Exception {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsServiceBean());
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    /**
     * UserDetailsService를 구성해준다. Bean Name은 반드시 BeanIds.USER_DETAILS_SERVICE로 등록되어야지 된다.
     * @return
     * @throws Exception
     */
    @Override
    @Bean(name = BeanIds.USER_DETAILS_SERVICE)
    public UserDetailsService userDetailsServiceBean() throws Exception {
        CommonAppUserDetailService userDetailsService = new CommonAppUserDetailService();
        userDetailsService.setUserService(context.getBean(UserService.class));
        return userDetailsService;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                    return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.equals(encodedPassword);
            }
        };
    }


5. AuthenticationManager 구성

AuthenticationProvider를 이용해서 AuthenticationProvider를 구성해준다. 코드는 매우 단순하다. 다만 개인적으로는 Form Login 부분을 customizing을 시키는 경우가 많기 때문에 AuthenticationManager를 Bean으로 등록시켜두면 두고두고 편한 일들이 많다. 


    /**
     * AuthenticationManager를 LoginController에서 사용하기 위해서는 반드시, @Bean으로 등록되어야지 된다.
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


전체 코드는 다음과 같다. 


/**
 * Created with IntelliJ IDEA.
 * User: ykyoon
 * Date: 11/18/13
 * Time: 1:12 AM
 * To change this template use File | Settings | File Templates.
 */

@EnableWebSecurity
@Configuration
@Slf4j
@ComponentScan("co.kr.daesung.app.center.api.web.cors")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    public static final String CORS_SUPPORT_FILTER = "corsSupportFilter";
    public static final String ADMIN_ROLE = "ADMIN";

    @Autowired
    private ApplicationContext context;
    @Autowired
    private CorsSupportLoginUrlAuthenticationEntryPoint corsEntryPoint;

    @Bean
    public DigestAuthenticationFilter digestAuthenticationFilter() throws Exception {
        DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
        digestAuthenticationFilter.setAuthenticationEntryPoint(corsEntryPoint);
        digestAuthenticationFilter.setUserDetailsService(userDetailsServiceBean());
        return digestAuthenticationFilter;
    }

    @Bean
    public LoginProcessHandler loginProcessHandler() {
        LoginProcessHandler loginProcessHandler = new LoginProcessHandler();
        loginProcessHandler.setObjectMapper(objectMapper());
        return loginProcessHandler;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                    .loginPage("/login")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .defaultSuccessUrl("/main")
                    .failureUrl("/login?error=loginFailed")
                .and()
                .authorizeRequests()
                    .antMatchers("/api/admin/**").hasRole(ADMIN_ROLE)
                    .antMatchers("/api/apiKey/**").authenticated()
                    .antMatchers("/api/message/**").authenticated()
                    .antMatchers("/api/auth/**").authenticated();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    /**
     * AuthenticationManager를 LoginController에서 사용하기 위해서는 반드시, @Bean으로 등록되어야지 된다.
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * UserDetailService와 PasswordEncoder를 이용해서, AuthenticationProvider를 구성한다.
     * @return
     * @throws Exception
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() throws Exception {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsServiceBean());
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    /**
     * UserDetailsService를 구성해준다. Bean Name은 반드시 BeanIds.USER_DETAILS_SERVICE로 등록되어야지 된다.
     * @return
     * @throws Exception
     */
    @Override
    @Bean(name = BeanIds.USER_DETAILS_SERVICE)
    public UserDetailsService userDetailsServiceBean() throws Exception {
        CommonAppUserDetailService userDetailsService = new CommonAppUserDetailService();
        userDetailsService.setUserService(context.getBean(UserService.class));
        return userDetailsService;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                    return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.equals(encodedPassword);
            }
        };
    }

    @Bean(name = CORS_SUPPORT_FILTER)
    public Filter corsSupportFilter() {
        CorsSupportFilter corsSupportFilter = new CorsSupportFilter();
        return corsSupportFilter;
    }
}

6. no-web.xml인 경우 등록하는 방법


Servlet 3.0에서 지원되는 web.xml이 없는 개발에서 매우 유용한 방법이다. Spring Security는 Web.xml이 없을때, 아주 쉬운 코드로 WebXmlInitializer를 구성해뒀는데, AbstractSecurityWebApplicationInitializer를 상속받은 Class가 class path에 있는 경우, 자동으로 springSecurityFilterChain 이름의 Bean을 이용하는 DelegateProxyFilter를 등록한다. web.xml인 경우에는 기존과 동일하게 사용하면 된다. 다만 root context에 @Configuration이 로드가 먼저 선행되어야지 된다. 


public class SecurityWebXmlInitializer extends AbstractSecurityWebApplicationInitializer {
}


Posted by Y2K
,