{{page_title}}

{{page_description}}


When Spring Framework was first released in 2003, as an IoC container, Spring Framework is using XML as the configuration file format ever since. Spring 2.0, which was released in 2006, introduced XML Schema-based configuration. And since Spring 2.5, annotation-based configuration was used. Later Spring 3.0 was released in 2009, with Java Config project being merged into the core framework.

I started using Spring since version 1, so I'm used to XML configuration. It is quite good compared to other config solution I used before. To me, it was awkward at beginning, then after a while, I got familiar with it and it became comfortable to read. And then XM Schema, Java annotation. Each upgrade makes my config file smaller and more clear to read. But when I found Spring Java Config project, I was immediately embracing it. In my opinion, configuration is part of programming, and config files should be treated as code.

In my JiwhizBlog application code, I use Java config as my configuration solution, and I like the code very much. In some cases I still use Java annotation-based config, like all my SpringMVC controller classes. I feel my controller classes are very simple, and all dependencies are injected into controller through @Inject annotation. Those controller classes are annotated with @Controller, and I just put @ComponentScan(basePackages = "com.jiwhiz.blog.web") to my /src/main/java/com/jiwhiz/blog/config/WebConfig class.

But there is still one XML config file left, for Spring Security, see /src/main/resources/security.xml. The reason is currently Spring Security 3.1.x release does not support Java config.

Spring Security team made tremendous efforts to get security configuration easy. You can see the huge progress after http namespace was introduced in Spring Security. The default config becomes extremely succinct, like the following minimal settings:

  
    
  

That lowered the entry barriers of Spring Security, and made developer's life easier. But, there always a but, the great conciseness brings a big cost for other less common usage of security settings. Because the default http namespace hides all the complexities behind, it is much harder to customize it to fit your special scenarios. For example, it took me a while to figure out how to make Remember Me work because I'm using Spring Social to do authentication. See my post: Add RememberMe Authentication With Spring Security

The final result is not bad, although I still want to get rid of the last piece of XML code. It is like itching on my back.

Recently I found Spring Security Lead Rob Winch is working on a project called Spring Security Java Config, and I want to give it a try although it is not released yet. After reading the examples, I figured out how to use it and created a branch for my JiwhizBlogWeb project. You can see the code at https://github.com/jiwhiz/JiwhizBlogWeb/tree/java-config

I broke my old SocialAndSecurityConfig into two config classes, SocialConfig for social related. And SecurityConfig for security. My SecurityConfig is a sub-class of WebSecurityConfigurerAdapter, and annotated with @EnableWebSecurity and @EnableGlobalMethodSecurity(prePostEnabled=true). I moved security or Rememebr Me related bean to this class, and override some methods to replace my old security.xml settings. The code is like this:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Inject
    private Environment environment;

    @Inject
    private UserAdminService userAdminService;

    @Inject
    private RememberMeTokenRepository rememberMeTokenRepository;

    @Inject
    private UsersConnectionRepository usersConnectionRepository;

    @Inject
    private SocialAuthenticationServiceLocator socialAuthenticationServiceLocator;

    @Bean
    public SocialAuthenticationFilter socialAuthenticationFilter() throws Exception{
        SocialAuthenticationFilter filter = new SocialAuthenticationFilter(authenticationManager(), userAdminService,
                usersConnectionRepository, socialAuthenticationServiceLocator);
        filter.setFilterProcessesUrl("/signin");
        filter.setSignupUrl(null);
        filter.setConnectionAddedRedirectUrl("/myAccount");
        filter.setPostLoginUrl("/myAccount"); //always open account profile page after login
        filter.setRememberMeServices(rememberMeServices());
        return filter;
    }

    @Bean
    public SocialAuthenticationProvider socialAuthenticationProvider(){
        return new SocialAuthenticationProvider(usersConnectionRepository, userAdminService);
    }

    @Bean
    public LoginUrlAuthenticationEntryPoint socialAuthenticationEntryPoint(){
        return new LoginUrlAuthenticationEntryPoint("/signin");
    }

    @Bean
    public RememberMeServices rememberMeServices(){
        PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices(
                        environment.getProperty("application.key"), userAdminService, persistentTokenRepository());
        rememberMeServices.setAlwaysRemember(true);
        return rememberMeServices;
    }

    @Bean
    public RememberMeAuthenticationProvider rememberMeAuthenticationProvider(){
        RememberMeAuthenticationProvider rememberMeAuthenticationProvider =
                        new RememberMeAuthenticationProvider(environment.getProperty("application.key"));
        return rememberMeAuthenticationProvider;
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        return new MongoPersistentTokenRepositoryImpl(rememberMeTokenRepository);
    }

    @Override
    protected void ignoredRequests(IgnoredRequestRegistry ignoredRequests) {
        ignoredRequests
            .antMatchers("/resources/**");
    }

    @Override
    protected void authorizeUrls(ExpressionUrlAuthorizations interceptUrls) {
        interceptUrls
            .antMatchers("/favicon.ico", "/robots.txt").permitAll()
            .antMatchers("/presentation/**").hasRole("USER")
            .antMatchers("/myAccount/**").hasRole("USER")
            .antMatchers("/myPost/**").hasRole("AUTHOR")
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/**").permitAll();
    }


	@Override
	protected void configure(HttpConfigurator http) throws Exception {
        http
	        .authenticationEntryPoint(socialAuthenticationEntryPoint())
	        .addFilterBefore(socialAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
	        .logout()
	            .deleteCookies("JSESSIONID")
	            .logoutUrl("/signout")
	            .logoutSuccessUrl("/")
	            .permitAll()
	            .and()
	        .rememberMe()
	        	.rememberMeServices(rememberMeServices());
	}

    @Override
    protected void registerAuthentication(AuthenticationRegistry registry) throws Exception{
        registry
        	.add(socialAuthenticationProvider())
        	.add(rememberMeAuthenticationProvider())
        	.add(userAdminService);
    }
}

I feel the code is much easier to read. I like the way my config is written in Method Chaining style. Although Method Chaining is a very subjective coding style, see the discussion in Stack Overflow. I feel it fits very well in configuration, so the code is more readable. It looks like a DSL for security configuration, and it matches old http namespace XML code.

One caveat I found: for any hasRole(role) method call, the passing parameter role string will be the name of the role without ROLE_ prefix. For example, admin user authority is ROLE_ADMIN, and in security.xml, we protect admin pages by using <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>. But in our Java config, we use .antMatchers("/admin/**").hasRole("ADMIN").

Another important feature of Spring Security is HTTP/HTTPS Channel Security. It seems very easy to set it in my SecurityConfig according to the test code NamespaceHttpPortMappingsTests.groovy . I can just add three lines of code to secure the admin pages with HTTPS only access:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
	@Override
	protected void configure(HttpConfigurator http) throws Exception {
        http
	        .authenticationEntryPoint(socialAuthenticationEntryPoint())
	        .addFilterBefore(socialAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
	        .logout()
	            .deleteCookies("JSESSIONID")
	            .logoutUrl("/signout")
	            .logoutSuccessUrl("/")
	            .permitAll()
	            .and()
	        .rememberMe()
	        	.rememberMeServices(rememberMeServices())
	        	.add()
	        .requiresChannel()
                .antMatchers("/signin","/admin/**").requiresSecure()
                .antMatchers("/**").requiresInsecure();
	}
...
}

Since Cloud Foundry has issues with HTTPS, I cannot test it with my demo website on CloudFoundry.com. From the CloudFoundry.com support forum discussion Is it possible to visit my application via SSL (HTTPS)?:

Cloud Foundry is currently implemented to terminate SSL connections at the router, and all internal communication is unencrypted HTTP.

So I will test secure channel config after I upgrade my AppFog account.

Update: Now I moved my website to Pivotal Web Services, and use Cloud Flare to handle SSL ,and it works very well.