javascript - headers - ¿Cómo configurar CORS en una aplicación Spring Boot+Spring Security?
spring security cors (13)
Utilizo Spring Boot con Spring Security y Cors Support.
Si ejecuto el siguiente código
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
Como resultado
200
Si pruebo con credenciales incorrectas como
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
en lugar de obtener 401 (ese es el código estándar para la autenticación incorrecta en la seguridad de primavera) me sale
0
con la siguiente notificación del navegador:
OBTENGA http://localhost:5000/api/token
XMLHttpRequest no puede cargar http: // localhost: 5000 . No hay encabezado ''Access-Control-Allow-Origin'' presente en el recurso solicitado. Por lo tanto, el origen '' http: // localhost: 3000 '' no tiene acceso permitido. La respuesta tenía el código de estado HTTP 401.
Estoy desarrollando un código front-end que necesita códigos de estado http útiles de las respuestas del servidor para manejar la situación. Necesito algo más útil que 0. También el cuerpo de respuesta está vacío. No sé si mi configuración es incorrecta, o si es un error de software y tampoco sé dónde, si es cromo (usando Arch Linux) o Spring Security.
Mi Spring Config es:
@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();
}
}
Si pruebo con curl, todo funciona perfecto, creo que porque no se necesita soporte de CORS, pero traté de simular el CORS con solicitudes de OPCIÓN y el resultado también estuvo bien.
$ 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"}
y con credenciales incorrectas:
$ 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"}
Editar: para evitar malentendidos. Yo uso 1.3.3 Spring Boot. La publicación del blog escribe:
El soporte de CORS estará disponible en la próxima versión Spring Boot 1.3, y ya está disponible en las compilaciones 1.3.0.BUILD-SNAPSHOT.
El uso de la configuración CORS del método del controlador con anotaciones @CrossOrigin en su aplicación Spring Boot no requiere ninguna configuración específica.
La configuración global de CORS se puede definir registrando un bean WebMvcConfigurer con un método personalizado addCorsMappings (CorsRegistry):
He agregado el siguiente código para habilitar el soporte global de cors. De hecho, he intentado esto antes, pero el resultado fue el mismo. Lo intenté nuevamente recientemente y el resultado es el mismo.
@Configuration
public class MyConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}
Sin embargo, la idea de que el problema proviene de una redirección entre el proceso de autorización es interesante. ¿Cómo puedo cambiar la redirección a cualquier recurso para evitar este conflicto?
EDITAR:
Supongo que estoy más cerca de una solución. He probado con mi servidor nodejs que admite cors sin problemas agregando Access-Control-Allow-Origin: * a todas las solicitudes.
Como Stefan Isele ya ha mencionado, parece que la seguridad de primavera redirige o no agrega el encabezado CORS, por eso la solicitud parece estar rota. Entonces, mientras Spring Security verifica la autenticación, debe agregar el encabezado adecuado.
¿Alguien sabe cómo hacerlo?
EDITAR:
Encontré una solución, que parece ser fea. He comenzado un problema de github para el arranque de primavera donde describo la solución: https://github.com/spring-projects/spring-boot/issues/5834
Cors puede ser un fastidio, pero con este código simple, ¡solo eres Cors! al método especificado
@CrossOrigin(origins="*")// in this line add your url and thats is all for spring boot side
@GetMapping("/some")
public String index() {
return "pawned cors!!!!";
}
Como un encanto en la bota de primavera 2.0.2
Después de mucho buscar el error proveniente de javascript CORS, la única solución elegante que encontré para este caso fue configurar los cors de la propia clase de Spring org.springframework.web.cors.CorsConfiguration.CorsConfiguration ()
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
}
Encontré una solución fácil para Spring-Boot, Spring-Security y la configuración basada en Java:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return new CorsConfiguration().applyPermitDefaultValues();
}
});
}
}
Estaba teniendo problemas importantes con Axios, Spring Boot y Spring Security con autenticación.
Tenga en cuenta la versión de Spring Boot y Spring Security que está utilizando.
Spring Boot: 1.5.10 Spring: 4.3.14 Spring Security 4.2.4
Para resolver este problema utilizando la Configuración Java basada en anotaciones, creé la siguiente clase:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("youruser").password("yourpassword")
.authorities("ROLE_USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().
authorizeRequests()
.requestMatchers(CorsUtils:: isPreFlightRequest).permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.realmName("Biometrix");
http.csrf().disable();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization"));
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Una de las principales trampas con Axios es que cuando su API requiere autenticación, envía un encabezado de autorización con la solicitud OPTIONS. Si no incluye la Autorización en la configuración de encabezados permitidos, nuestra solicitud OPTIONS (también conocida como solicitud PreFlight) fallará y Axios informará un error.
Como puede ver con un par de configuraciones simples y correctamente colocadas, la configuración de CORS con SpringBoot es bastante fácil.
La protección de origen cruzado es una característica del navegador. A Curl no le importan CORS, como suponías. Eso explica por qué sus rizos son exitosos, mientras que las solicitudes del navegador no lo son.
Si envía la solicitud del navegador con las credenciales incorrectas, spring intentará reenviar al cliente a una página de inicio de sesión. Esta respuesta (fuera de la página de inicio de sesión) no contiene el encabezado ''Access-Control-Allow-Origin'' y el navegador reacciona como usted describe.
Debe hacer spring para incluir el haeder para esta respuesta de inicio de sesión, y puede ser para otra respuesta, como páginas de error, etc.
Esto se puede hacer así:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}
Esto se copia de cors-support-in-spring-framework
Comenzaría agregando mapeo cors para todos los recursos con:
registry.addMapping("/**")
y también permite todos los encabezados de los métodos. Una vez que funciona, puede comenzar a reducirlo nuevamente al mínimo necesario.
Tenga en cuenta que la configuración de CORS cambia con la versión 4.2.
Si esto no resuelve sus problemas, publique la respuesta que obtiene de la solicitud fallida de ajax.
Para la configuración de propiedades
# ENDPOINTS CORS CONFIGURATION (EndpointCorsProperties)
endpoints.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported.
endpoints.cors.allowed-headers= # Comma-separated list of headers to allow in a request. ''*'' allows all headers.
endpoints.cors.allowed-methods=GET # Comma-separated list of methods to allow. ''*'' allows all methods.
endpoints.cors.allowed-origins= # Comma-separated list of origins to allow. ''*'' allows all origins. When not set, CORS support is disabled.
endpoints.cors.exposed-headers= # Comma-separated list of headers to include in a response.
endpoints.cors.max-age=1800 # How long, in seconds, the response from a pre-flight request can be cached by clients.
Resolví este problema de la siguiente manera:
@Configuration
public class CORSFilter extends CorsFilter {
public CORSFilter(CorsConfigurationSource source) {
super((CorsConfigurationSource) source);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Headers",
"Access-Control-Allow-Origin, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
if (response.getHeader("Access-Control-Allow-Origin") == null)
response.addHeader("Access-Control-Allow-Origin", "*");
filterChain.doFilter(request, response);
}
}
y:
@Configuration
public class RestConfig {
@Bean
public CORSFilter corsFilter() {
CorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:4200");
config.addAllowedMethod(HttpMethod.DELETE);
config.addAllowedMethod(HttpMethod.GET);
config.addAllowedMethod(HttpMethod.OPTIONS);
config.addAllowedMethod(HttpMethod.PUT);
config.addAllowedMethod(HttpMethod.POST);
((UrlBasedCorsConfigurationSource) source).registerCorsConfiguration("/**", config);
return new CORSFilter(source);
}
}
Resolví este problema: `
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers","Access-Control-Allow-Origin","Access-Control-Request-Method", "Access-Control-Request-Headers","Origin","Cache-Control", "Content-Type", "Authorization"));
configuration.setAllowedMethods(Arrays.asList("DELETE", "GET", "POST", "PATCH", "PUT"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
``
Si está utilizando Spring Security , puede hacer lo siguiente para asegurarse de que las solicitudes CORS se manejen primero:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
...
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Vea Spring 4.2.x CORS para más información.
Sin Spring Security esto funcionará:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS");
}
};
}
Si usa JDK 8+, hay una solución lambda de una línea:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
}
Solución de Kotlin
...
http.cors().configurationSource {
CorsConfiguration().applyPermitDefaultValues()
}
...
Spring Security ahora puede aprovechar el soporte Spring MVC CORS descrito en esta publicación de blog que escribí.
Para que funcione, debe habilitar explícitamente el soporte de CORS en el nivel de Spring Security de la siguiente manera; de lo contrario, Spring Security puede bloquear las solicitudes habilitadas para CORS antes de llegar a Spring MVC.
Si está utilizando anotaciones de nivel de controlador
@CrossOrigin
, solo tiene que habilitar el soporte de Spring Security CORS y aprovechará la configuración de Spring MVC:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()...
}
}
Si prefiere usar la configuración global de CORS, puede declarar un bean
CorsConfigurationSource
siguiente manera:
@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;
}
}
Este enfoque reemplaza el enfoque basado en filtros previamente recomendado.
Puede encontrar más detalles en la sección dedicada CORS de la documentación de Spring Security.
Tuve el mismo problema en un método que devuelve el estado del servidor. La aplicación se implementa en múltiples servidores. Así que lo más fácil que encontré es agregar
@CrossOrigin(origins = "*")
@RequestMapping(value="/schedulerActive")
public String isSchedulerActive(){
//code goes here
}
Este método no es seguro, pero puede agregar
allowCredentials
para eso.