java - Filtro de seguridad de primavera CORS
spring spring-security (9)
Agregamos
Spring Security
a nuestro proyecto existente.
A partir de este momento obtenemos un
No ''Access-Control-Allow-Origin'' header is present on the requested resource
401
No ''Access-Control-Allow-Origin'' header is present on the requested resource
error de
No ''Access-Control-Allow-Origin'' header is present on the requested resource
de nuestro servidor.
Esto se debe a que no se adjunta ningún encabezado
Access-Control-Allow-Origin
a la respuesta.
Para solucionar esto, agregamos nuestro propio filtro que se encuentra en la cadena
Filter
antes del filtro de cierre de sesión, pero el filtro no se aplica a nuestras solicitudes.
Nuestro error:
XMLHttpRequest no puede cargar
http://localhost:8080/getKunden
. No hay encabezado ''Access-Control-Allow-Origin'' presente en el recurso solicitado. Origenhttp://localhost:3000
por lo tanto, no se permite el acceso. La respuesta tenía el código de estado HTTP 401.
Nuestra configuración de seguridad:
@EnableWebSecurity
@Configuration
@ComponentScan("com.company.praktikant")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilter filter;
@Override
public void configure(HttpSecurity http) throws Exception {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
source.registerCorsConfiguration("/**", config);
http.addFilterBefore(new MyFilter(), LogoutFilter.class).authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/*").permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
}
}
Nuestro filtro
@Component
public class MyFilter extends OncePerRequestFilter {
@Override
public void destroy() {
}
private String getAllowedDomainsRegex() {
return "individual / customized Regex";
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String origin = "http://localhost:3000";
response.addHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers",
"content-type, x-gwt-module-base, x-gwt-permutation, clientid, longpush");
filterChain.doFilter(request, response);
}
}
Nuestra aplicación
@SpringBootApplication
public class Application {
public static void main(String[] args) {
final ApplicationContext ctx = SpringApplication.run(Application.class, args);
final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(CORSConfig.class);
annotationConfigApplicationContext.refresh();
}
}
Nuestro filtro está registrado desde spring-boot:
2016-11-04 09: 19: 51.494 INFO 9704 --- [ost-startStop-1] osbwservlet.FilterRegistrationBean: Filtro de mapeo: ''myFilter'' a: [/ *]
Nuestra cadena de filtros generada:
2016-11-04 09: 19: 52.729 INFO 9704 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creando cadena de filtros: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework .security.web.context.request.async.WebAsyncManagerIntegrationFilter @ 5d8c5a8a, org.springframework.security.web.context.SecurityContextPersistenceFilter@7d6938f, org.springframework.security.web.header.Heater. .csrf.CsrfFilter @ 4af4df11, com.company.praktikant.MyFilter@5ba65db2, org.springframework.security.web.authentication.logout.LogoutFilter@2330834f, org.springframework.security.web.savedrequest.RequestCategoría. .security.web.servletapi.SecurityContextHolderAwareRequestFilter @ 4fc0f1a2, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2357120f, org.springframework.security.web.session.SessionManagement. nslationFilter @ 4b8bf1fb, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@42063cf1]
La respuesta: encabezados de respuesta
¡También probamos la solución desde la primavera pero no funcionó! La anotación @CrossOrigin en nuestro controlador tampoco ayudó.
Editar 1:
Probé la solución de @Piotr Sołtysiak. El filtro cors no aparece en la cadena de filtros generada y todavía recibimos el mismo error.
2016-11-04 10: 22: 49.881 INFO 8820 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creando una cadena de filtro: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework .security.web.context.request.async.WebAsyncManagerIntegrationFilter @ 4c191377, org.springframework.security.web.context.SecurityContextPersistenceFilter@28bad32a, org.springframework.security.web.header.HeaderWriter. .csrf.CsrfFilter @ 288460dd, org.springframework.security.web.authentication.logout.LogoutFilter@1c9cd096, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3990.33.f.ejemplo.ejemplo. @ 1e8d4ac1, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@2d61d2a4, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@380d9a9b, org.springframework.security.sepi. questFilter @ abf2de3, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2a5c161b, org.springframework.security.web.session.SessionManagementFilter@3c1fd3e5, org.springframework.acf. security.web.access.intercept.FilterSecurityInterceptor@5d27725a]
Por cierto, estamos utilizando Spring-Security versión 4.1.3.!
-
No necesitas:
@Configuration @ComponentScan("com.company.praktikant")
@EnableWebSecurity
ya tiene@Configuration
, y no puedo imaginar por qué pones a@ComponentScan
allí. -
Sobre el filtro CORS, simplemente pondría esto:
@Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; }
En SecurityConfiguration clase y eliminar configurar y configurar métodos globales. No necesita configurar orgins, encabezados y métodos allowde dos veces. Especialmente si pones diferentes propiedades en el filtro y la configuración de seguridad de primavera :)
-
Según lo anterior, su clase "MyFilter" es redundante.
-
También puedes eliminar esos:
final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(CORSConfig.class); annotationConfigApplicationContext.refresh();
De la clase de aplicación.
-
Al final, un pequeño consejo: no está relacionado con la pregunta. No quieres poner verbos en URI. En lugar de
http://localhost:8080/getKunden
, debe usar el método HTTP GET enhttp://localhost:8080/kunden
resource. Puede obtener información sobre las mejores prácticas para el diseño RESTful api aquí: http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
Con Spring Security en Spring Boot 2 para configurar CORS a nivel mundial (p. Ej., Todas las solicitudes de desarrollo habilitadas) puede hacer:
@Bean
protected CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}
Dado que tuve problemas con las otras soluciones (especialmente para que funcione en todos los navegadores, por ejemplo, edge no reconoce "*" como un valor válido para "Access-Control-Allow-Methods"), tuve que usar un personalizado componente de filtro, que al final funcionó para mí e hizo exactamente lo que quería lograr.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
final ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:3000");
}
};
}
}
Desde Spring Security 4.1, esta es la forma correcta de hacer que Spring Security sea compatible con CORS (también se necesita en Spring Boot 1.4 / 1.5):
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
y:
@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"));
// setAllowCredentials(true) is important, otherwise:
// The value of the ''Access-Control-Allow-Origin'' header in the response must not be the wildcard ''*'' when the request''s credentials mode is ''include''.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
No haga nada de lo siguiente, que son la forma incorrecta de intentar resolver el problema:
-
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
-
web.ignoring().antMatchers(HttpMethod.OPTIONS);
Referencia: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html
En mi caso, acabo de agregar esta clase y uso @EnableAutConfiguration
package com.package.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class SimpleCORSFilter extends GenericFilterBean {
/**
* The Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
logger.info("> doFilter");
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
//response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, resp);
logger.info("< doFilter");
}
}
En muchos lugares, veo la respuesta que necesita agregar este código:
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
pero en mi caso, arroja una excepción de tipo de clase inesperada.
corsFilter()
requiere el tipo
CorsFilter
, así que he hecho estos cambios y puse esta definición de bean en mi configuración y todo está bien ahora.
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
Ok, después de más de 2 días de búsqueda, finalmente solucionamos el problema. Eliminamos todos nuestros filtros y configuraciones y, en su lugar, utilizamos estas 5 líneas de código en la clase de aplicación.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods",
"ACL, CANCELUPLOAD, CHECKIN, CHECKOUT, COPY, DELETE, GET, HEAD, LOCK, MKCALENDAR, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, REPORT, SEARCH, UNCHECKOUT, UNLOCK, UPDATE, VERSION-CONTROL");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key, Authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
public void init(FilterConfig filterConfig) {
// not needed
}
public void destroy() {
//not needed
}
}
Para los programas ya implementados y que no pueden permitirse los cambios de código (por ejemplo, agregar / actualizar la seguridad de primavera), agregar un proxy simple es una solución: https://.com/a/49827300/1758194
Según la documentación del filtro CORS :
"Spring MVC proporciona soporte preciso para la configuración de CORS a través de anotaciones en los controladores. Sin embargo, cuando se usa con Spring Security, es recomendable confiar en el CorsFilter incorporado que debe pedirse antes de la cadena de filtros de Spring Security".
Algo como esto permitirá el acceso
GET
a
/ajaxUri
:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AjaxCorsFilter extends CorsFilter {
public AjaxCorsFilter() {
super(configurationSource());
}
private static UrlBasedCorsConfigurationSource configurationSource() {
CorsConfiguration config = new CorsConfiguration();
// origins
config.addAllowedOrigin("*");
// when using ajax: withCredentials: true, we require exact origin match
config.setAllowCredentials(true);
// headers
config.addAllowedHeader("x-requested-with");
// methods
config.addAllowedMethod(HttpMethod.OPTIONS);
config.addAllowedMethod(HttpMethod.GET);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/startAsyncAuthorize", config);
source.registerCorsConfiguration("/ajaxUri", config);
return source;
}
}
Por supuesto, su configuración de SpringSecurity debe permitir el acceso al URI con los métodos enumerados. Ver la respuesta de @Hendy Irawan.