davidtinker / grails-cors

Grails plugin to add Cross-Origin Resource Sharing (CORS) headers
38 stars 21 forks source link

Basic Auth filter authenticates OPTIONS requests and breaks CORS #12

Closed jamesdh closed 11 years ago

jamesdh commented 11 years ago

When using this plugin along with the Spring Security basic auth filter, CORS breaks down because it seems the auth is getting applied to the OPTIONS requests as well. This causes the options requests to return with a status of 401 which in turn breaks CORS on pretty much any browser.

It seems fair that the basic auth filter would get applied to all request types. I'm just wondering if there is some way we can override the default filter behavior when used in conjunction with this plugin. I'd be willing to put in the work and submit a PR but am just throwing this out there for ideas before I get started on it!

jamesdh commented 11 years ago

oh and by the way....THANK YOU FOR MAKING THIS PLUGIN!!!!! :+1:

davidtinker commented 11 years ago

Thanks! It would be cool if you could have a look at this issue and submit a PR. I am having a lot of "data migration fun" with my day job at the moment and will be only partially net connected next week while on holiday.

On 14 Aug 2013, at 4:03 PM, James Hardwick notifications@github.com wrote:

oh and by the way....THANK YOU FOR MAKING THIS PLUGIN!!!!!

— Reply to this email directly or view it on GitHub.

christianjunk commented 11 years ago

Hi @jamesdh,

I ran into the same problems, but I think I found a solution regarding to this article:

http://www.bmchild.com/2013/05/spring-security-return-401-unauthorized.html

Here are the steps to solve this issue:

1) Create a Java class called CustomBasicAuthenticationEntryPoint and put it into your Java Sources directory

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.util.ELRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomAuthenticationEntryPoint extends
        BasicAuthenticationEntryPoint {

    private static final RequestMatcher requestMatcher = new ELRequestMatcher(
            "hasHeader('X-Requested-With','XMLHttpRequest')");

    public CustomAuthenticationEntryPoint() {
        super();
    }

    public CustomAuthenticationEntryPoint(String realmName) {
        setRealmName(realmName);
    }

    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {

        if(isPreflight(request)){
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
        } else if (isRestRequest(request)) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        } else {
            super.commence(request, response, authException);
        }
    }

    /**
     * Checks if this is a X-domain pre-flight request.
     * @param request
     * @return
     */
    private boolean isPreflight(HttpServletRequest request) {
        return "OPTIONS".equals(request.getMethod());
    }

    /**
     * Checks if it is a rest request
     * @param request
     * @return
     */
    protected boolean isRestRequest(HttpServletRequest request) {
        return requestMatcher.matches(request);
    }
}

2) Open resources.groovyand add the following lines:

// Place your Spring DSL code here
beans = {

    basicAuthenticationEntryPoint(CustomAuthenticationEntryPoint) { bean ->
        realmName = 'Your Realm'
    }
}

That should do the trick ...

Regards, Christian

jamesdh commented 11 years ago

Hi @christianjunk! That's a bit of a different problem then the one I described. You're speaking to the header which causes the browser to pop-up the auth prompt. The problem I've described is that the OPTIONS request in particular shouldn't be authenticated at all and allowed to proceed without getting a 401 error. But I'm guessing your approach is the same path or pretty close to the one I should take to fix it, so thanks for the help!

christianjunk commented 11 years ago

@jamesdh Please take a look at the code of my updated answer. This will check if the request is an OPTION request or not. If so it will even pass if the resource need authentication.

jamesdh commented 11 years ago

@christianjunk Yea, that's pretty close to what I did last night :) I'm not going to do anything w/ the header which causes a browser prompt for login, as I believe (?) that is standard/expected behavior anyways. We can look more into that separately.

One question...does the CORS standard dictate that the OPTIONS preflight not contain any content (hence your SC_NO_CONTENT) and only headers? I just simply checked if it was an OPTIONS request and called the overridden method if so, otherwise made no changes (as in I didn't set the status to no content).

davidtinker commented 11 years ago

Thanks for the PR. I merge it on the weekend.

ghost commented 9 years ago

Hi - christianjunk it looks like RequestMatcher is an deprecated method. Could you please provide the newer one? Also one can answer on very similar question? http://stackoverflow.com/questions/32725249/cors-filter-issue-for-authentrypoint

ceefour commented 7 years ago

Since Spring Security 4.1, this is the proper way to make Spring Security support CORS (also needed in Spring Boot 1.4/1.5):

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.csrf().disable();
        http.cors();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(ImmutableList.of("*"));
        configuration.setAllowedMethods(ImmutableList.of("HEAD",
                "GET", "POST", "PUT", "DELETE", "PATCH"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Reference: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html

ShurikAg commented 2 years ago

Since Spring Security 4.1, this is the proper way to make Spring Security support CORS (also needed in Spring Boot 1.4/1.5):

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.csrf().disable();
        http.cors();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(ImmutableList.of("*"));
        configuration.setAllowedMethods(ImmutableList.of("HEAD",
                "GET", "POST", "PUT", "DELETE", "PATCH"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Reference: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html

I know it's been a while. In grails 5, how do I make this configuration even being loaded. It seems like it is not loaded for me. Do I need to map it somehow?