I use Spring Boot with Spring Security and Cors Support.
If I execute following code
url = 'http://localhost:5000/api/token' xmlhttp = new XMLHttpRequest xmlhttp.onreadystatechange = -> if xmlhttp.readyState is 4 console.log xmlhttp.status xmlhttp.open "GET", url, true # xmlhttp.setRequestHeader "X-Requested-With", "XMLHttpRequest" xmlhttp.setRequestHeader 'Authorization', 'Basic ' + btoa 'a:a' do xmlhttp.send
I get as a result
200
If I test with wrong credentials like
url = 'http://localhost:5000/api/token' xmlhttp = new XMLHttpRequest xmlhttp.onreadystatechange = -> if xmlhttp.readyState is 4 console.log xmlhttp.status xmlhttp.open "GET", url, true # xmlhttp.setRequestHeader "X-Requested-With", "XMLHttpRequest" xmlhttp.setRequestHeader 'Authorization', 'Basic ' + btoa 'a:aa' do xmlhttp.send
instead of getting 401 (that is the standard code for wrong authentication in spring security) I get
0
with following browser notification:
GET http://localhost:5000/api/token
XMLHttpRequest cannot load http://localhost:5000. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 401.
I'm developing front-end code that needs useful http status codes from server responses to handle the situation. I need something more useful than 0. Also the response body is empty. I dont know if my config is wrong, or it's a software bug and I also don't know where, if it's chromium (using arch linux) or spring security.
My Spring Config is:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @RestController @RequestMapping("api") public class Controller { @RequestMapping("token") @CrossOrigin Map<String, String> token(HttpSession session) { return Collections.singletonMap("token", session.getId()); } } @EnableWebSecurity public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("a").password("a").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .anyRequest().authenticated() .and().httpBasic(); } }
If I test with curl everything works perfect, I think because no CORS support needed, but I tried to simulate the CORS with OPTION requests and the result was also ok.
$ curl -v localhost:5000/api/token -H "Authorization: Basic YTpha" * Trying ::1... * Connected to localhost (::1) port 5000 (#0) > GET /api/token HTTP/1.1 > Host: localhost:5000 > User-Agent: curl/7.48.0 > Accept: */* > Authorization: Basic YTpha > < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cache < Expires: 0 < X-Frame-Options: DENY < Access-Control-Allow-Origin: http://localhost:3000 < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE < Access-Control-Max-Age: 3600 < Access-Control-Allow-Credentials: true < Access-Control-Allow-Headers: Origin,Accept,X-Requested- With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization < x-auth-token: 58e4cca9-7719-46c8-9180-2fc16aec8dff < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Sun, 01 May 2016 16:15:44 GMT < * Connection #0 to host localhost left intact {"token":"58e4cca9-7719-46c8-9180-2fc16aec8dff"}
and with wrong credentials:
$ curl -v localhost:5000/api/token -H "Authorization: Basic YTp" * Trying ::1... * Connected to localhost (::1) port 5000 (#0) > GET /api/token HTTP/1.1 > Host: localhost:5000 > User-Agent: curl/7.48.0 > Accept: */* > Authorization: Basic YTp > < HTTP/1.1 401 Unauthorized < Server: Apache-Coyote/1.1 < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cache < Expires: 0 < X-Frame-Options: DENY < WWW-Authenticate: Basic realm="Realm" < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Sun, 01 May 2016 16:16:15 GMT < * Connection #0 to host localhost left intact {"timestamp":1462119375041,"status":401,"error":"Unauthorized","message":"Failed to decode basic authentication token","path":"/api/token"}
Edit: To avoid misunderstandings. I use 1.3.3 Spring Boot. The Blog post writes:
CORS support will be available in the upcoming Spring Boot 1.3 release, and is already available in the 1.3.0.BUILD-SNAPSHOT builds.
Using controller method CORS configuration with @CrossOrigin annotations in your Spring Boot application does not require any specific configuration.
Global CORS configuration can be defined by registering a WebMvcConfigurer bean with a customized addCorsMappings(CorsRegistry) method:
I have added following code to enable global cors support. actually I have tried this before but it the result was the same. I tried it again recently and the result is the same.
@Configuration public class MyConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } }; } }
The idea, that the problem comes from a redirect between the authorization process is an interesting though. how can i change the redirect to any resources to avoid this conflict?
EDIT:
I guess I am closer to a solution. I have tested with my nodejs server that supports cors without problems by adding Access-Control-Allow-Origin: * to all requests.
Like Stefan Isele has already mentioned it seems that spring security redirects or doesn't add the CORS header so that's why the request seems to be broken. So while spring security is checking the authentification it has to add the proper header.
Does anyone know how to do so?
EDIT:
I found a workaround, that seems to be ugly. I have started a github issue for spring boot where I describe the workaround: https://github.com/spring-projects/spring-boot/issues/5834
Controller Method CORS ConfigurationThis @CrossOrigin annotation enables cross-origin resource sharing only for this specific method. By default, its allows all origins, all headers, and the HTTP methods specified in the @RequestMapping annotation. Also, a maxAge of 30 minutes is used.
27.2 Controller method CORS configuration You can add an @CrossOrigin annotation to your @RequestMapping annotated handler method in order to enable CORS on it.
To enable CORS support through Spring security, configure CorsConfigurationSource bean and use HttpSecurity. cors() configuration.
Spring Security can now leverage Spring MVC CORS support described in this blog post I wrote.
To make it work, you need to explicitly enable CORS support at Spring Security level as following, otherwise CORS enabled requests may be blocked by Spring Security before reaching Spring MVC.
If you are using controller level @CrossOrigin
annotations, you just have to enable Spring Security CORS support and it will leverage Spring MVC configuration:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and()... } }
If you prefer using CORS global configuration, you can declare a CorsConfigurationSource
bean as following:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and()... } @Bean CorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); return source; } }
This approach supersedes the filter-based approach previously recommended.
You can find more details in the dedicated CORS section of Spring Security documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With