CORS con spring-boot y angularjs no funcionan

Estoy tratando de llamar a los puntos finales REST en una aplicación (aplicación de arranque de resorte) desde otra (angularjs). Las aplicaciones se ejecutan en los siguientes hosts y puertos.

  • Aplicación REST, usando el arranque de spring, http://localhost:8080
  • Aplicación HTML, usando angularjs, http://localhost:50029

También estoy usando spring-security con la aplicación Spring-boot. Desde la aplicación HTML, puedo autenticarme en la aplicación REST, pero, a partir de entonces, todavía no puedo acceder a ningún punto final REST. Por ejemplo, tengo un servicio angularjs definido de la siguiente manera.

 adminServices.factory('AdminService', ['$resource', '$http', 'conf', function($resource, $http, conf) { var s = {}; s.isAdminLoggedIn = function(data) { return $http({ method: 'GET', url: 'http://localhost:8080/api/admin/isloggedin', withCredentials: true, headers: { 'X-Requested-With': 'XMLHttpRequest' } }); }; s.login = function(username, password) { var u = 'username=' + encodeURI(username); var p = 'password=' + encodeURI(password); var r = 'remember_me=1'; var data = u + '&' + p + '&' + r; return $http({ method: 'POST', url: 'http://localhost:8080/login', data: data, headers: {'Content-Type': 'application/x-www-form-urlencoded'} }); }; return s; }]); 

El controlador angularjs tiene el siguiente aspecto.

 adminControllers.controller('LoginController', ['$scope', '$http', 'AdminService', function($scope, $http, AdminService) { $scope.username = ''; $scope.password = ''; $scope.signIn = function() { AdminService.login($scope.username, $scope.password) .success(function(d,s) { if(d['success']) { console.log('ok authenticated, call another REST endpoint'); AdminService.isAdminLoggedIn() .success(function(d,s) { console.log('i can access a protected REST endpoint after logging in'); }) .error(function(d, s) { console.log('huh, error checking to see if admin is logged in'); $scope.reset(); }); } else { console.log('bad credentials?'); } }) .error(function(d, s) { console.log('huh, error happened!'); }); }; }]); 

En la llamada a http://localhost:8080/api/admin/isloggedin , obtengo un 401 Unauthorized .

En el lado de la aplicación REST, tengo un filtro CORS que se parece al siguiente.

 @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CORSFilter implements Filter { @Override public void destroy() { } @Override 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", "http://localhost:50029"); response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token"); response.setHeader("Access-Control-Allow-Credentials", "true"); if(!"OPTIONS".equalsIgnoreCase(request.getMethod())) { chain.doFilter(req, res); } } @Override public void init(FilterConfig config) throws ServletException { } } 

Mi configuración de seguridad de spring se parece a la siguiente.

 @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; @Autowired private JsonAuthSuccessHandler jsonAuthSuccessHandler; @Autowired private JsonAuthFailureHandler jsonAuthFailureHandler; @Autowired private JsonLogoutSuccessHandler jsonLogoutSuccessHandler; @Autowired private AuthenticationProvider authenticationProvider; @Autowired private UserDetailsService userDetailsService; @Autowired private PersistentTokenRepository persistentTokenRepository; @Value("${rememberme.key}") private String rememberMeKey; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .authenticationEntryPoint(restAuthenticationEntryPoint) .and() .authorizeRequests() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/", "/admin", "/css/**", "/js/**", "/fonts/**", "/api/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .successHandler(jsonAuthSuccessHandler) .failureHandler(jsonAuthFailureHandler) .permitAll() .and() .logout() .deleteCookies("remember-me", "JSESSIONID") .logoutSuccessHandler(jsonLogoutSuccessHandler) .permitAll() .and() .rememberMe() .userDetailsService(userDetailsService) .tokenRepository(persistentTokenRepository) .rememberMeCookieName("REMEMBER_ME") .rememberMeParameter("remember_me") .tokenValiditySeconds(1209600) .useSecureCookie(false) .key(rememberMeKey); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .authenticationProvider(authenticationProvider); } } 

Todos los manejadores están escribiendo una respuesta JSON como {success: true} función de si el usuario inició sesión, no se autenticó o se desconectó. RestAuthenticationEntryPoint tiene el siguiente aspecto.

 @Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex) throws IOException, ServletException { resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } } 

¿Alguna idea sobre lo que me estoy perdiendo o haciendo mal?

 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class SimpleCORSFilter implements Filter { private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class); public SimpleCORSFilter() { log.info("SimpleCORSFilter init"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me"); chain.doFilter(req, res); } @Override public void init(FilterConfig filterConfig) { } @Override public void destroy() { } } 

No es necesario definir este filtro, solo agregue esta clase. Spring se escaneará y lo agregará por usted. SimpleCORSFilter. Aquí está el ejemplo: spring-enable-cors

Yo había estado en una situación similar. Después de investigar y probar, estos son mis hallazgos:

  1. Con Spring Boot, la forma recomendada de habilitar CORS global es declarar dentro de Spring MVC y combinar con la configuración @CrossOrigin grano fino como:

     @Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*") .allowedHeaders("*"); } }; } } 
  2. Ahora, ya que está utilizando Spring Security, debe habilitar CORS en el nivel de Spring Security para permitirle aprovechar la configuración definida en Spring MVC level como:

     @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and()... } } 

    Aquí hay un excelente tutorial que explica el soporte de CORS en el marco Spring MVC.

Para mí, lo único que funcionó al 100% cuando se usa la seguridad springl fue omitir toda la pelusa adicional de filtros y frijoles adicionales y cualquier persona “mágica” indirecta sugirió que funcionaba para ellos, pero no para mí.

En su lugar, simplemente StaticHeadersWriter a escribir los encabezados que necesita con un StaticHeadersWriter simple:

 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // your security config here .authorizeRequests() .antMatchers(HttpMethod.TRACE, "/**").denyAll() .antMatchers("/admin/**").authenticated() .anyRequest().permitAll() .and().httpBasic() .and().headers().frameOptions().disable() .and().csrf().disable() .headers() // the headers you want here. This solved all my CORS problems! .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Origin", "*")) .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Methods", "POST, GET")) .addHeaderWriter(new StaticHeadersWriter("Access-Control-Max-Age", "3600")) .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Credentials", "true")) .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization")); } } 

Esta es la forma más directa y explícita que encontré para hacerlo. Espero que ayude a alguien.

Si desea habilitar CORS sin usar filtros o sin archivo de configuración solo agregue

 @CrossOrigin 

a la parte superior de su controlador y funciona.

revisa este:

 @Override protected void configure(HttpSecurity httpSecurity) throws Exception { ... .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() ... } 

Esto funciona para mí:

 @Configuration public class MyConfig extends WebSecurityConfigurerAdapter { //... @Override protected void configure(HttpSecurity http) throws Exception { //... http.cors().configurationSource(new CorsConfigurationSource() { @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration config = new CorsConfiguration(); config.setAllowedHeaders(Collections.singletonList("*")); config.setAllowedMethods(Collections.singletonList("*")); config.addAllowedOrigin("*"); config.setAllowCredentials(true); return config; } }); //... } //... } 

La ampliación de la clase WebSecurityConfigurerAdapter y la anulación del método de configuración () en su clase @EnableWebSecurity funcionarían: A continuación se muestra la clase de muestra

 @Override protected void configure(final HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling(); http.headers().cacheControl(); @Override public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) { return new CorsConfiguration().applyPermitDefaultValues(); } }); } } 

Si originalmente su progtwig no utiliza seguridad de spring y no puede permitirse un cambio de código, la creación de un proxy inverso simple puede hacer el truco. En mi caso, utilicé Nginx con la siguiente configuración:

 http { server { listen 9090; location / { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # # Custom headers and headers various browsers *should* be OK with but aren't # add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; # # Tell client that this pre-flight info is valid for 20 days # add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; } proxy_pass http://localhost:8080; } } } 

Mi progtwig escucha : 8080 .

REF: CORS en Nginx