Adrian Lopez
Adrian Lopez

Reputation: 1784

Basic and form based authentication with Spring security Javaconfig

I'm trying to define two different security configurations for different url patterns, one of them using form login and another one using basic authentication for an api.

The solution I'm looking for is similar to the one explained here http://meera-subbarao.blogspot.co.uk/2010/11/spring-security-combining-basic-and.html but I would like to do it using java config.

Thanks in advance.

This is the configuration I currently have:

@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Override
    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // Ignore any request that starts with "/resources/".
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeUrls().antMatchers("/", "/index", "/user/**", "/about").permitAll()
        .antMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
        .and().formLogin()
        .loginUrl("/login")
        .failureUrl("/login-error")
        .loginProcessingUrl("/security_check")
        .usernameParameter("j_username").passwordParameter("j_password")
        .permitAll();

        http.logout().logoutUrl("/logout");
        http.rememberMe().rememberMeServices(rememberMeServices()).key("password");
    }

    @Bean
    public RememberMeServices rememberMeServices() {
        TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", userService);
        rememberMeServices.setCookieName("cookieName");
        rememberMeServices.setParameter("rememberMe");
        return rememberMeServices;
    }
}

Upvotes: 8

Views: 19236

Answers (5)

Tim Schimandle
Tim Schimandle

Reputation: 802

Obviously As Spring updates these tend to fade in their applicability. Running spring cloud starter security 1.4.0.RELEASE this is my solution. My use case was a bit different as I'm trying to secure the refresh endpoint using basic auth for cloud configuration and using a gateway with spring session to pass the authentication in all other instances.

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal1(AuthenticationManagerBuilder auth) throws Exception {
        //try in memory auth with no users to support the case that this will allow for users that are logged in to go anywhere
        auth.inMemoryAuthentication();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .httpBasic()
                .disable()
            .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/user").permitAll()
                .anyRequest().authenticated()
                .and()
            .csrf().disable();
    }

    @Bean
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder(11);
    }

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Autowired
        private AuthenticationProvider customAuthenticationProvider;

        @Autowired
        protected void configureGlobal2(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .authenticationProvider(customAuthenticationProvider);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .httpBasic()
                    .and()
                .authorizeRequests()
                    .antMatchers(HttpMethod.POST, "/refresh").hasRole("SUPER")
                    .and()
                .csrf().disable();
        }
    }
}

see the spring security docs for further clarification: Spring Security

The basic idea is that the @Order annotation will dictate what order the auth schemes are run. No @Order means that it is last. If the authorizeRequests() section cannot match on the incoming URL then that configuration will pass and the next one will attempt authentication. This will proceed until authentication succeeds or fails.

Upvotes: 0

ETL
ETL

Reputation: 10011

The other answers in here are relatively old and for example, I can't find authorizeUrls in Spring 4.

In SpringBoot 1.4.0 / Spring 4, I've implemented the basic/form login like this:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
   protected void configure (HttpSecurity aHttp) throws Exception
   {    
      aHttp.authorizeRequests ().antMatchers (("/api/**")).fullyAuthenticated ().and ().httpBasic ();

      aHttp.formLogin ()
         .loginPage ("/login").permitAll ()
         .and ().logout ().permitAll ();
   }
}

There may be more ellegant ways of writing this - I'm still working on understanding how this builder works in terms of sequence and so forth. But this worked.

Upvotes: -1

perenono
perenono

Reputation: 1

You can solve it by adding .antMatcher("/api/**") just after http in your first config to manage only /api urls. You must have it on the first adapter:

http
    .antMatcher("/api/*")
    .authorizeRequests()
        .antMatchers("^/api/.+$").hasRole("ADMIN")
    ....

Upvotes: 0

Adrian Lopez
Adrian Lopez

Reputation: 1784

The solution I found was to create another class extending WebSecurityConfigurerAdapter inside the first one, like is described https://github.com/spring-projects/spring-security-javaconfig/blob/master/samples-web.md#sample-multi-http-web-configuration

My solution is as follows:

@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Override
    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // Ignore any request that starts with "/resources/".
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeUrls().antMatchers("/", "/index", "/user/**", "/about").permitAll()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and().formLogin()
            .loginUrl("/login")
            .failureUrl("/login-error")
            .loginProcessingUrl("/security_check")
            .usernameParameter("j_username").passwordParameter("j_password")
            .permitAll();

        http.logout().logoutUrl("/logout");
        http.rememberMe().rememberMeServices(rememberMeServices()).key("password");
    }

    @Bean
    public RememberMeServices rememberMeServices() {
        TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", userService);
        rememberMeServices.setCookieName("cookieName");
        rememberMeServices.setParameter("rememberMe");
        return rememberMeServices;
    }

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("api").password("pass").roles("API");
        }

        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeUrls()
                .antMatchers("/api/**").hasRole("API")
                .and()
                .httpBasic();
        }
    }
}

Upvotes: 13

M. Deinum
M. Deinum

Reputation: 124909

I would say by simply doing it. Specify a second line with authorizeUrls() but for your URLs that are needed with basic authentication. Instead of formLogin() use httpBasic()

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeUrls().antMatchers("/", "/index", "/user/**", "/about").permitAll()
    .antMatchers("/admin/**").hasRole("ADMIN")
    .anyRequest().authenticated()
    .and().formLogin()
    .loginUrl("/login")
    .failureUrl("/login-error")
    .loginProcessingUrl("/security_check")
    .usernameParameter("j_username").passwordParameter("j_password")
    .permitAll();

    http.authorizeUrls().antMatchers("/api/*").hasRole("YOUR_ROLE_HERE").and().httpBasic();

    http.logout().logoutUrl("/logout");
    http.rememberMe().rememberMeServices(rememberMeServices()).key("password");
}

Something like that should work.

Links: HttpSecurity, HttpBasicConfgurer.

Upvotes: 3

Related Questions