0

I have a fairly simplistic Spring Security configuration in my Spring MVC web app. I am receiving REST requests from a remote web application that get authenticated through my preauth filter. I simply look for a header in the request, and if present, I return that String value (which is the username) as the authentication object. This works fine and when a request comes in, the user gets authenticated.

For whatever reason, any time I attempt to enforce a pattern-based security antMatcher or controller method annotated security, it results in the client getting a CORS error. I have to keep the access fully open to allow the actual request to take place in this cross-domain environment.

My issue is, after a while, the session seems to expire. When a user hits one of my controllers and I try to get the username from the Principal object, I get a NullPointerException because the Principal is null. Once it expires, it looks like the AnonymousAuthFilter kicks in and just lets the user connect as anonymous.

I'm assuming I don't want a completely stateless authentication session, because I'm also using the session for websockets, and when I use my messaging template to "convertAndSendToUser", I need to provide the username and Spring needs to lookup the websocket session. However, I would like to force the security chain to recognize if the incoming request does not have a principal assigned, to force the request back through my PreAuthFilter and re-establish the session.

Is this possible?

Here is a quick rundown of my configure method in my security config file:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // Create authentication providers and authentication manager
        List<AuthenticationProvider> authenticationProviders = new ArrayList<>(1);
        AuthenticationProvider authProvider = preAuthenticatedAuthenticationProvider();
        authenticationProviders.add(authProvider);
        AuthenticationManager authenticationManager = authenticationManager(authenticationProviders);

        // Create the pre-authentication filter
        MyPreAuthFilter myPreAuthFilter = myPreAuthFilter(authenticationManager);
        myPreAuthFilter.setAuthenticationManager(authenticationManager);

        // configure http security
        http
                .csrf().disable()
                .headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
                .and()
                .authenticationProvider(authProvider)
                .addFilter(myPreAuthFilter)
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/stomp/**").permitAll()
                .antMatchers("/**").permitAll();
    }

UPDATE: So I just learned about this method on the http object:

.anonymous().disable()

However, when I use that, all my CORS requests fail again. I think the issue is specifically the preflight OPTIONS requests. This can't be authenticated -- it needs to be anonymously accessible. So I think to get the behavior I want out of my cross-domain authentication, I need to disable the anonymous filter, but doing so breaks one of the key tenants of being able to perform cross-domain requests. Ugh. Anybody know how to disable anonymous for all requests besides OPTIONS?

Bal
  • 2,027
  • 4
  • 25
  • 51

1 Answers1

2

Put in the CORS Filter before the spring security filter chain in your web application initializer (or web.xml) to do away the CORS issue (allow the REST request's origin by setting Access-Control-Allow-Origin to match to your request origin).

Then you can use role based (through ant matchers and JSR-250 annotations) authorization. Disabling anonymous users doesn't seem to be a good idea for the use case you have provided.

Abhinav Rai
  • 273
  • 2
  • 15
  • We're using Tomcat's built in CORS filter, so the server will always respond with the appropriate headers in the preflight OPTIONS request. With the CORS filter in place, we don't get CORS issues, unless I attempt to implement security controls on the methods or path patterns. – Bal Jul 16 '15 at 13:10
  • Oh! Sorry for the typo. Meant to say that register the CorsFilter before the springSecurityFilterChain. Let me explain, when you implement your security controls, spring security expression voters return -1 and an AccessDeniedException is thrown (as user is anonymous after the NullPointerException). This results into your request getting forwarded to the authentication entry point which responds with a 401 to your browser. It means, the CorsFilter never gets fired as it lies behind the DelegatingFilterProxy and Access-Control-Allow-Origin header never gets injected in the response. – Abhinav Rai Jul 16 '15 at 16:25
  • So I'm not using any application level CORS filter, I'm using the built-in Tomcat CORS filter at the container level, configured in Tomcat's web.xml. That being said, I swore I tried this before and it didn't work, but I simply changed my last antMatcher from "permitAll()" to "hasRole("USER"). Now the OPTIONS requests are getting through but the user will get a 403 if they don't have the preauth token in their header. Will this scenario force a request back through my preauth filter once the session has expired? – Bal Jul 17 '15 at 17:32
  • Oh! Never realized that you would be using the CorsFilter at container level. My bad. Answering your question, normally an AuthenticationEntryPoint will be called by the ExceptionTranslationFilter but things can be configured for a [Pre-authentication scenario](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#preauth). [Request-Header Authentication (Siteminder)](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#request-header-authentication-siteminder) seems like the thing you are looking for. Hope this helps :) – Abhinav Rai Jul 17 '15 at 19:15
  • Very interesting... So I'm extending the AbstractPreAuthenticatedProcessingFilter and looking for the desired header myself, which seems to be exactly what the RequestHeaderAuthenticationFilter is doing in that example. So my config matches up pretty much exactly with that example.... hopefully I will be good now! Thank you very much for your insight :) – Bal Jul 17 '15 at 19:33