1

I am writing a spring boot application in which I am registering a URL to a bean via the SimpleUrlHandlerMapping configuration. Why am I not using the @Controller or @RequestMapping classes to do this ?!! Because I want to dynamically register URL's during runtime.

I am using the following code to register a simple URL to a controller

    @Bean
    public SimpleUrlHandlerMapping sampleServletMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(Integer.MAX_VALUE - 2);

        Properties urlProperties = new Properties();
        urlProperties.put("/index", "myController");

        mapping.setMappings(urlProperties);

        return mapping;
    }

The above code is working fine, I am able to hit the controller bean registered with the name "myController".

The issue appears when I use spring security. I introduced spring security and configured InMemoryAuthentication, and set my configuration as follows.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/index").permitAll()
                .anyRequest()
                .permitAll();
    }

After doing this when I try to access /index path, it throws a 403, forbidden error. I have tried with permitAll() and fullyAuthenticated() configurations. It doesn't seem to work. However, any Controller class registered with the @Controller and @RequestMapping annotations are perfectly working fine with Security.

So, my assumption is that Spring Security is not aware of the dynamically registered URL's via the SimpleUrlHandlerMapping.

How do I solve this ? Is there a way I can tell spring security to include my dynamic URL registrations ? Unable to find any article on this online.

Suggestions and help much appreciated.

1 Answers1

0

UPDATE:

Why csrf().disable() does works

CSRF stands for Cross Site Request Forgery

In simple words, it is one kind of token that is sent with the request to prevent the attacks. In order to use the Spring Security CSRF protection, we'll first need to make sure we use the proper HTTP methods for anything that modifies the state (PATCH, POST, PUT, and DELETE – not GET).

CSRF protection with Spring CookieCsrfTokenRepository works as follows:

  1. The client makes a GET request to Server (Spring Boot Backend), e.g. request for the main page
  2. Spring sends the response for GET request along with Set-cookie header which contains securely generated XSRF Token
  3. The browser sets the cookie with XSRF Token
  4. While sending a state-changing request (e.g. POST) the client (might be angular) copies the cookie value to the HTTP request header
  5. The request is sent with both header and cookie (browser attaches the cookie automatically)
  6. Spring compares the header and the cookie values, if they are the same the request is accepted, otherwise, 403 is returned to the client

The method withHttpOnlyFalse allows angular to read XSRF cookie. Make sure that Angular makes XHR request with withCreddentials flag set to true.

For more details, you may explore the following

Updated method configure(HttpSecurity http)

 http
            .csrf()
            .ignoringAntMatchers("endpoint-to-be-ignored-for-csrf")
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            .authorizeRequests()
            .antMatchers("/index").permitAll() 
            .anyRequest().authenticated();

Endpoint specified in antMatchers with permitAll() should not required authentication and antMatchers("/index").permitAll() should work fine.

  • Make sure your security configuration class is annotated with @EnableWebSecurity and @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
  • The security configuration class is in follows the package structure and scanned by Spring. spring-component-scanning

You may find the minimal working example here


SecurityConfiguration.java

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // https://stackoverflow.com/a/56389047/10961238 -> WebSecurity vs HttpSecurity

    // Add this method if .antMatchers("/index").permitAll() does not work
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.debug(true);
//        web
//            .ignoring()
//            .antMatchers("/index");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/index").permitAll() //commenting this line will be results in 403
            .anyRequest().authenticated();
    }

}

SampleController.java

import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller("myController")
public class SampleController extends AbstractController {
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("::::::::::::::::::::::::::::::::Controller:::::::::::::::::::::::::::::::::");
        response.getWriter().print("Hello world!");
        return null;
    }
}

MainApplication.java

import com.example.mappings.controller.SampleController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;

import java.util.Properties;

@SpringBootApplication
public class MappingsApplication {

    public static void main(String[] args) {
        SpringApplication.run(MappingsApplication.class, args);
    }

    @Bean
    public SimpleUrlHandlerMapping sampleServletMapping() {
        System.out.println("::::::::::::::::::::::::::::::::SimpleUrlHandlerMapping:::::::::::::::::::::::::::::::::");
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(Integer.MAX_VALUE - 2);

        Properties urlProperties = new Properties();
        urlProperties.put("/index", sampleController());
        mapping.setMappings(urlProperties);

        return mapping;
    }

    @Bean
    public SampleController sampleController() {
        System.out.println("::::::::::::::::::::::::::::::::Setting SampleController:::::::::::::::::::::::::::::::::");
        return new SampleController();
    }
}

application.properties

spring.security.user.name = user
spring.security.user.password = user
spring.security.user.roles = ADMIN
Romil Patel
  • 12,879
  • 7
  • 47
  • 76
  • Thanks for the answer, this works but its got nothing to do with `@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)` or with `configure(WebSecurity web)` . I added `csrf().disable()` and it worked. Why have you disabled csrf ? I am not sure I would want to do that. – Kousick Shanmugam Nagaraj Jun 16 '20 at 17:40
  • I have updated the answer with more details on CSRF and why it has worked and way to add CSRF. I have added the annotation and method as an alternative way as I am not aware of all the details of project – Romil Patel Jun 16 '20 at 18:13
  • 1
    Thanks for your explanation. This worked and the explanation also makes sense. – Kousick Shanmugam Nagaraj Jun 17 '20 at 07:37