CORS avec Spring-Boot et Angularjs ne fonctionnent pas

J’essaie d’appeler les points de terminaison REST sur une application (application Spring-Boot) à partir d’une autre application (angularjs). Les applications s’exécutent sur les hôtes et les ports suivants.

  • Application REST, en utilisant le démarrage du spring, http://localhost:8080
  • Application HTML, utilisant angularjs, http://localhost:50029

J’utilise également spring-security avec l’application Spring-Boot. À partir de l’application HTML, je peux m’authentifier auprès de l’application REST, mais, par la suite, je ne peux toujours pas accéder à aucun sharepoint terminaison REST. Par exemple, j’ai un service angularjs défini comme suit.

 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; }]); 

Le contrôleur angularjs ressemble à ceci:

 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!'); }); }; }]); 

Sur l’appel à http://localhost:8080/api/admin/isloggedin , je reçois un 401 Unauthorized .

Du côté de l’application REST, j’ai un filtre CORS qui ressemble à ce qui suit.

 @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 { } } 

Ma configuration de spring security ressemble à ceci.

 @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 Ssortingng 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); } } 

Tout ce que font les gestionnaires est en train d’écrire une réponse JSON comme {success: true} selon que l’utilisateur s’est connecté, n’a pas pu s’authentifier ou s’est déconnecté. Le RestAuthenticationEntryPoint ressemble à ce qui suit.

 @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"); } } 

Des idées sur ce qui me manque ou ce que je fais 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() { } } 

Plus besoin de définir ce filtre, ajoutez simplement cette classe. Le spring sera analysé et ajouté pour vous. SimpleCORSFilter. Voici l’exemple: spring-enable-cors

J’étais dans la même situation. Après avoir effectué des recherches et des tests, voici mes conclusions:

  1. Avec Spring Boot, la méthode recommandée pour activer le système CORS global consiste à déclarer dans Spring MVC et combiné avec la configuration @CrossOrigin à @CrossOrigin fine en @CrossOrigin que:

     @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. Maintenant que vous utilisez Spring Security, vous devez également activer CORS au niveau de la sécurité Spring pour lui permettre de tirer parti de la configuration définie au niveau de Spring MVC comme suit:

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

    Voici un excellent tutoriel expliquant le support de CORS dans le framework Spring MVC.

Cela fonctionne pour moi:

 @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; } }); //... } //... } 

Pour moi, la seule chose qui fonctionnait à 100% lorsque la sécurité du spring était utilisée était de sauter tous les bourrelets supplémentaires de filtres et de haricots supplémentaires, et les personnes “magiques” indirectes continuaient à suggérer que cela fonctionnait pour eux mais pas pour moi.

Au lieu de cela, forcez-le à écrire les en-têtes dont vous avez besoin avec un simple StaticHeadersWriter :

 @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")); } } 

C’est la manière la plus directe et la plus explicite que j’ai trouvée de le faire. J’espère que ça aide quelqu’un.

Si vous souhaitez activer CORS sans utiliser de filtres ou sans fichier de configuration, ajoutez simplement

 @CrossOrigin 

en haut de votre contrôleur et cela fonctionne.

vérifier celui-ci:

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

Étendre la classe WebSecurityConfigurerAdapter et remplacer la méthode configure () dans votre classe @EnableWebSecurity fonctionnerait: Ci-dessous se trouve un exemple de classe

 @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, à l’origine, votre programme n’utilise pas la sécurité du spring et ne peut pas se permettre un changement de code, la création d’un simple proxy inverse peut faire l’affaire. Dans mon cas, j’ai utilisé Nginx avec la configuration suivante:

 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; } } } 

Mon programme écoute : 8080 .

REF: CORS sur Nginx