3

I have a Spring Boot REST application which has two main parts:

  • UI where I want to protect the ajax calls with a token
  • public endpoints where I want to have Basic Auth

As far as I understand I can't protect the public endpoints with CSRF tokens, as these need a session. The problem is, some endpoints need to be reachable by both, so how can I protect them with CSRF when it is used by the UI and disable CSRF for Basic Auth?

Here is what I currently have, where I disable csrf completely so basic works...

http.requestMatchers().antMatchers("/form/fill", "/form/fill/*", "/form/fillParams", "/form/fillParams/*").and()
                .csrf().disable().authorizeRequests().anyRequest().hasAnyRole(SecurityConfiguration.ROLE_FORMS_AUTHOR,
                        SecurityConfiguration.ROLE_FORM_FILLER, SecurityConfiguration.ROLE_ADMIN)
                .and().httpBasic();

EDIT: I found this old answer and I wonder if there is a way I can leverage this for my case, but I'm still not sure how to distinguish between a "local" user and one that is authenticated with httpBasic()

Thomas
  • 6,325
  • 4
  • 30
  • 65
  • For public urls make .permitAll() and for anyrequest should be authenticated. – kj007 Oct 17 '18 at 14:55
  • @kj007 Maybe I formulated it wrong, but the "public" ones should still be protected with httpBasic, but httpBasic and CSRF token wont work together unless you have additional "handshake" requests to retrieve the token, which would invalidate the RESTfulness. – Thomas Oct 17 '18 at 15:17

2 Answers2

4

In your Spring Security java configuration file you can configure the HttpSecurity object as follows in order to enable the CSRF check only on some requests (by default is enabled on all the incoming requests, and disable will disable for all incoming request so request Mather can help here for path you want to enable or disable csrf.).

Make sure to replace /urls-with-csrf-check/** with your paths by end point or multiple paths..

 @Override
    protected void configure(HttpSecurity http) throws Exception {

        RequestMatcher csrfRequestMatcher = new RequestMatcher() {
            private RegexRequestMatcher requestMatcher =
                    new RegexRequestMatcher("/urls-with-csrf-check/**", null);

            public boolean matches(HttpServletRequest httpServletRequest) {
                if (requestMatcher.matches(httpServletRequest)) {
                    return true;
                }
                return false;
            }
        };

        http.requestMatchers().antMatchers("/form/fill", "/form/fill/*", "/form/fillParams", "/form/fillParams/*").and()
                .csrf()
                .requireCsrfProtectionMatcher(csrfRequestMatcher)
                .and()
                .authorizeRequests().anyRequest().hasAnyRole(SecurityConfiguration.ROLE_FORMS_AUTHOR, SecurityConfiguration.ROLE_FORM_FILLER, SecurityConfiguration.ROLE_ADMIN)
                .and().httpBasic();
    }
kj007
  • 6,073
  • 4
  • 29
  • 47
  • Thanks, I now just need to figure out a way to identify a local user. I managed to check for ajax with "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) but there are some normal form submits. The only thing that came into my mind was a hidden field and check for it's presence, but that is not very safe. – Thomas Oct 19 '18 at 14:49
  • You can define role for http basic ?? If I got you correctly – kj007 Oct 19 '18 at 14:53
  • good point, I could check the user role, but how do I get the role from just the HttpServletRequest? – Thomas Oct 19 '18 at 15:04
  • Spring security will check if Principal has authority "local" (you can use in ant matcher hasRole("local")) – kj007 Oct 19 '18 at 15:11
  • But how do I combine this within the requireCsrfProtectionMatcher, e.g. enable CSRF for this authority? – Thomas Oct 19 '18 at 15:31
  • Requirecsrfproetcion matcher should have something to also pass role, really need to check – kj007 Oct 19 '18 at 15:40
1

With the input from @kj007, I was able to get this working. I am using the requireCsrfProtectionMatcher and this is how my matcher looks like:

public class UIRequestMatcher implements RequestMatcher {

    public static final List<GrantedAuthority> USER_ROLES = new ArrayList<>();
    static {
        USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_ADMIN));
        USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_FILES_AUTHOR));
        USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_FORMS_AUTHOR));
        USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_TEMPLATES_AUTHOR));
    }

    @Override
    public boolean matches(HttpServletRequest request) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return "POST".equals(request.getMethod()) && auth.getAuthorities().stream().anyMatch(USER_ROLES::contains);
    }

}

So I am checking if the Authentication has any of my user roles, as my basic auth should only be used for my technical users.

Thomas
  • 6,325
  • 4
  • 30
  • 65