Search code examples
javaspringspring-mvcspring-securitypre-authentication

Spring Security AnonymousAuthFilter With PreAuthenticationFilter Allowing Unauthorized Requests


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?


Solution

  • 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.