web-dev-qa-db-ja.com

spring-bootとangularjsのCORSが機能しない

あるアプリケーション(スプリングブートアプリケーション)のRESTエンドポイントを別のアプリケーション(angularjs)から呼び出そうとしています。アプリケーションは、次のホストとポートで実行されています。

  • スプリングブートを使用するRESTアプリケーション、http://localhost:8080
  • Anglejsを使用したHTMLアプリケーション、http://localhost:50029

また、スプリングブートアプリケーションでspring-securityを使用しています。 HTMLアプリケーションから、RESTアプリケーションに対して認証できますが、その後、RESTエンドポイントにアクセスできません。たとえば、angularjsサービスは次のように定義されています。

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

Angularjsコントローラーは次のようになります。

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

http://localhost:8080/api/admin/isloggedinへの呼び出しで、401 Unauthorizedを取得します。

RESTアプリケーション側には、次のようなCORSフィルターがあります。

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

私の春のセキュリティ構成は次のようになります。

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

ハンドラーが行っていることは、ユーザーがログインしたか、認証に失敗したか、ログアウトしたかに基づいて、{success: true}のようなJSON応答を書き込むことです。 RestAuthenticationEntryPointは次のようになります。

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

}

不足していることや間違っていることに関するアイデア

65
Jane Wayne
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() {
}

}

このフィルタを追加で定義する必要はありません。このクラスを追加するだけです。 Springがスキャンして追加します。 SimpleCORSFilter。以下に例を示します。 spring-enable-cors

80
abosancic

私は同じような状況にあった。研究とテストを行った後、私の発見は次のとおりです。

  1. Spring Bootで、グローバルCORSを有効にする推奨される方法は、Spring MVC内で宣言し、次のように詳細な@CrossOrigin構成と組み合わせることです。

    @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. Spring Securityを使用しているため、Spring SecurityレベルでCORSを有効にし、Spring MVCレベルで定義された構成を次のように活用できるようにする必要があります。

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

    ここ は、Spring MVCフレームワークでのCORSサポートを説明する非常に優れたチュートリアルです。

25
Yogen Rai

フィルターを使用せずに、または構成ファイルなしでCORSを有効にする場合は、追加するだけです

@CrossOrigin

コントローラーの上部に移動すると動作します。

6
Eduardo Dennis

これは私のために働く:

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

      //...

   }

   //...

}
3
Renan Polisciuc

私はspring boot 2.1.0を使用していて、私にとってうまくいったのは

A.次の方法でcorsマッピングを追加します。

@Configuration
public class Config implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*");
    }
}

B.春のセキュリティのためにHttpSecurityに以下の設定を追加します

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

また、Zuulプロキシの場合、これを使用できますINSTEAD OF A and B(Springセキュリティで有効にするにはHttpSecurity.cors()を使用します):

@Bean
public CorsFilter corsFilter() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    final CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("OPTIONS");
    config.addAllowedMethod("HEAD");
    config.addAllowedMethod("GET");
    config.addAllowedMethod("PUT");
    config.addAllowedMethod("POST");
    config.addAllowedMethod("DELETE");
    config.addAllowedMethod("PATCH");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}
2
Sep GH

上記の他の答えに基づいて、Springセキュリティを備えたSpringブートRESTサービスアプリケーション(Spring MVCではない)がある場合、Springセキュリティを介してCORSを有効化するだけで十分です(Spring MVCを使用し、WebMvcConfigurerを使用する場合Yogenが言及したbeanは、Springのセキュリティがそこに言及されているCORS定義に委任するため、進むべき道です。

したがって、次のことを行うセキュリティ設定が必要です。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    //other http security config
    http.cors().configurationSource(corsConfigurationSource());
}

//This can be customized as required
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    List<String> allowOrigins = Arrays.asList("*");
    configuration.setAllowedOrigins(allowOrigins);
    configuration.setAllowedMethods(singletonList("*"));
    configuration.setAllowedHeaders(singletonList("*"));
    //in case authentication is enabled this flag MUST be set, otherwise CORS requests will fail
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

}

このリンクには、同じ詳細があります。 https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors

注:

  1. Prodがデプロイされたアプリケーションのすべてのオリジン(*)でCORSを有効にすることは、必ずしも良い考えではありません。
  2. CSRFは、Spring HttpSecurityカスタマイズを介して問題なく有効にできます。
  3. Springのアプリで認証を有効にしている場合(たとえばUserDetailsServiceを使用)、configuration.setAllowCredentials(true);を追加する必要があります

Spring boot 2.0.0.RELEASE(つまり、Spring 5.0.4.RELEASEおよびSpring security 5.0.3.RELEASE)のテスト済み

1
Deepak

私にとって、春のセキュリティが使用されたときに100%動作したのは、余分なフィルターとBeanのすべての追加フラフをスキップすることと、自分では機能しないが間接的に「魔法」の人々が提案し続けたことでした。

代わりに、必要なヘッダーをプレーンな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"));
    }
}

これが、私が見つけた最も直接的で明示的な方法です。それが誰かを助けることを願っています。

1
Jeronimo Backes

これをチェックしてください:

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

WebSecurityConfigurerAdapterクラスを拡張し、@ EnableWebSecurityクラスのconfigure()メソッドをオーバーライドすると動作します。以下はサンプルクラスです

@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();
        }
    });
   }
}
0
keyRen

元々プログラムがスプリングセキュリティを使用せず、コードを変更する余裕がない場合は、単純なリバースプロキシを作成することで問題を解決できます。私の場合、次の構成でNginxを使用しました。

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

私のプログラムは:8080をリッスンします。

REF: NginxのCORS

0
Orvyl