2017-05-31 6 views
4

私はAngular 4ウェブフロントエンドを持つSpring Boot REST APIを持っています。私はこれらのフレームワークの両方にとても満足しています。 1つの継続的な問題は、CORS要求に関連しています。それは、馬鹿のゲームのように感じる。問題が発生するたびに、すぐに別の1つがポップアップし、私の週末が荒れます。 問題なく、私の春のブート残りのAPIにリクエストを行うことができます。しかし、私の角度のウェブサイトのレスポンスからヘッダーを取得したいときは、利用可能なヘッダーは5つしかなく、現在最も懸念されているETagヘッダーを含めて、ほとんどが欠落しています。 私はいくつかのSOの投稿を読んで、私が必要とするヘッダーを公開するために私の角度のHTTP呼び出しにリクエストヘッダーを追加するだけでよいことを主張しています(デバッグコンソールでは私が期待しているヘッダーをすべて見てください)。 Angular2 Http Response missing header key/valuesから 提案はheaders.append('Access-Control-Expose-Headers', 'etag');角度4のHTTPコールでAPIレスポンスヘッダーのほとんどがフィルタリングされます

を追加することでした私はこれを試してみましたが、私は次のようerrrorを得る:「リクエストヘッダフィールドへのアクセス・コントロール・公開・ヘッダをプリフライト応じてアクセス制御 - 許可 - ヘッダによって許可されていません。」

私は正直なところこのメッセージで混乱しています。私は春のブートで私のCORSの設定のいくつかを微調整したが役に立たなかった。

私はこれとどこに行かないかわかりません。私はPHPで解決することができなかったこのような悪夢を経験したことがないので、私はJava + SpringブートからPHP(cringe)に戻って切り替えを検討しています。

ご意見がありましたらお手伝いください。

私の角度のフロントエンドに関連するコードは以下の通りです:

import {Injectable} from '@angular/core'; 
 
import {Http, RequestOptions, Response} from '@angular/http'; 
 
import {Post} from '../class/post'; 
 
import {Observable} from 'rxjs/Rx'; 
 
import 'rxjs/add/operator/mergeMap'; 
 
import 'rxjs/add/operator/map'; 
 

 

 
@Injectable() 
 
export class PostDaoService { 
 

 
    private jwt: String; 
 

 
    private commentsUrl = 'http://myapidomain/posts'; 
 

 
    private etag: string; 
 

 
    constructor(private http: Http, private opt: RequestOptions) { 
 
    // tslint:disable-next-line:max-line-length 
 
    this.jwt = 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJQYXNjYWwiLCJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.4D9TUDQAgIWAooyiMN1lV8Y5w56C3PKGzFzelSE9diqHMik9WE9x4EsNnEcxQXYATjxAZovpp-m72LpFADA'; 
 
    } 
 

 
    getPosts(trigger: Observable<any>): Observable<Array<Post>> { 
 
    this.opt.headers.set('Authorization', 'Bearer ' + this.jwt); 
 
    this.opt.headers.set('Content-Type', 'application/json'); 
 

 
    this.opt.headers.set('Access-Control-Expose-Headers', 'etag'); 
 
    if (this.etag !== null) { 
 
     this.opt.headers.set('If-None-Match', this.etag); 
 
    } 
 

 
    return trigger.mergeMap(() => 
 
     this.http.get(this.commentsUrl) 
 
     .map((response) => { 
 
      if (response.status === 304) { 
 
      alert('NO CHANGE TO REPOURCE COLLECTION'); 
 
      } else if (response.status === 200) { 
 
      console.log(response.headers); 
 
      console.log(response.text()); 
 
      return response.json()._embedded.posts as Post[]; 
 
      } 
 
     } 
 
    )); 
 
    } 
 

 
    submitPost(): Promise<Object> { 
 
    this.opt.headers.set('Authorization', 'Bearer ' + this.jwt); 
 
    this.opt.headers.set('Content-Type', 'application/json'); 
 
    return this.http.post(this.commentsUrl, JSON.stringify({text: 'some new text'})) 
 
     .toPromise() 
 
     .then(response => response.json()) 
 
     .catch(); 
 
    } 
 

 
}

と春ブーツアプリから(CORS設定で)Applicationクラスは以下の通りです:

@SpringBootApplication 
@EnableJpaRepositories("rest.api.repository") 
@EnableMongoRepositories("rest.api.repository") 
@EnableTransactionManagement 
@EnableConfigurationProperties 
@EnableCaching 
public class Application extends SpringBootServletInitializer{ 

public static final long LOGGED_IN_USER = 1L; 

public static void main(String[] args) { 
    SpringApplication.run(Application.class, args); 

} 

@Override 
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 

    return application.sources(Application.class); 
} 

@Bean 
public FilterRegistrationBean corsFilter() { 
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 
    CorsConfiguration config = new CorsConfiguration(); 
    config.setAllowCredentials(true); 
    config.addAllowedOrigin("*"); 
    config.addAllowedHeader("Access-Control-Expose-Headers"); 
    config.addAllowedHeader("X-Requested-With"); 
    config.addAllowedHeader("Authorization"); 
    config.addAllowedHeader("Content-Type"); 
    config.addAllowedHeader("If-None-Match"); 
    config.addAllowedHeader("Access-Control-Allow-Headers"); 

    config.addExposedHeader("Access-Control-Allow-Origin"); 
    config.addExposedHeader("Access-Control-Allow-Headers"); 
    config.addExposedHeader("ETag"); 
    config.addAllowedMethod("GET"); 
    config.addAllowedMethod("POST"); 
    config.addAllowedMethod("PUT"); 
    config.addAllowedMethod("DELETE"); 
    config.addAllowedMethod("OPTIONS"); 
    config.addAllowedMethod("HEAD"); 

    source.registerCorsConfiguration("/**", config); 
    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); 
    bean.setOrder(0); 
    return bean; 
} 
} 

と私のコントローラ:

@RepositoryRestController 
@CrossOrigin(methods = {RequestMethod.GET, 
    RequestMethod.POST, 
    RequestMethod.PUT, 
    RequestMethod.DELETE, 
    RequestMethod.OPTIONS, 
    RequestMethod.HEAD}) 
public class PostController { 

private PostRepository postRepository; 
private UserRepository userRepository; 
private LikeRepository likeRepository; 
private DislikeRepository dislikeRepository; 

@Autowired 
PagedResourcesAssembler pagedResourcesAssembler; 

protected PostController() { 
} 

@Autowired 
public PostController(PostRepository postRepository, UserRepository userRepository, LikeRepository likeRepository, DislikeRepository dislikeRepository) { 
    this.postRepository = postRepository; 
    this.userRepository = userRepository; 
    this.likeRepository = likeRepository; 
    this.dislikeRepository = dislikeRepository; 
} 

@ResponseBody 
@RequestMapping(value = "/posts", method = RequestMethod.GET) 
public ResponseEntity<PagedResources<PersistentEntityResource>> getAll(HttpRequest request, 
                     Pageable pageable, 
                     PersistentEntityResourceAssembler resourceAssembler) { 
     Page<Post> page = postRepository.findAll(pageable); 
     return ResponseEntity 
       .ok() 
       .cacheControl(CacheControl.maxAge(5, TimeUnit.SECONDS)) 
       .eTag(String.valueOf(page.hashCode())) 
       .body(pagedResourcesAssembler.toResource(page, resourceAssembler)); 

} 

@ResponseBody 
@RequestMapping(value = "/posts", method = RequestMethod.POST) 
public ResponseEntity<PersistentEntityResource> sendPost(@RequestBody Post post, 
                 PersistentEntityResourceAssembler resourceAssembler, 
                 UriComponentsBuilder b) { 
    User sender = userRepository.findOne(1L); 
    URI loc = null; 
    post.setSender(sender); 
    post = postRepository.save(post); 

    UriComponents uriComponents = 
      b.path("/posts/{id}").buildAndExpand(post.getIdentify()); 

    HttpHeaders headers = new HttpHeaders(); 

    return ResponseEntity 
      .ok() 
      .cacheControl(CacheControl.maxAge(5, TimeUnit.SECONDS)) 
      .location(uriComponents.toUri()) 
      .eTag(String.valueOf(post.getVersion())) 
      .body(resourceAssembler.toFullResource(post)); 
} 

@ResponseBody 
@RequestMapping(value = "/posts/{id}", method = RequestMethod.PUT) 
public PersistentEntityResource edit(@PathVariable(value = "id") long id, @RequestBody Post post, PersistentEntityResourceAssembler resourceAssembler) { 
    Post editedPost = postRepository.findOne(id); 
    editedPost.setCreated(post.getCreated()); 
    editedPost.setText(post.getText()); 
    postRepository.save(editedPost); 
    return resourceAssembler.toFullResource(editedPost); 
} 

@ResponseBody 
@RequestMapping(value = "/posts/{id}/likes", method = RequestMethod.POST) 
public PersistentEntityResource likePost(@PathVariable(value = "id") long id, PersistentEntityResourceAssembler resourceAssembler) { 
    final boolean isAlreadyLiked = false; 

    User userWhoLikesIt = userRepository.findOne(1L); 
    Post post = postRepository.findOne(id); 
    post.setLiked(post.getLiked() + 1); 
    Likey like = new Likey(userWhoLikesIt); 
    likeRepository.save(like); 
    return resourceAssembler.toFullResource(like); 
} 

@ResponseBody 
@RequestMapping(value = "/posts/{id}/dislikes", method = RequestMethod.POST) 
public PersistentEntityResource dislikePost(@PathVariable(value = "id") long id, PersistentEntityResourceAssembler resourceAssembler) { 
    User userWhoDislikesIt = userRepository.findOne(1L); 
    DisLike dislike = new DisLike(userWhoDislikesIt); 
    dislikeRepository.save(dislike); 
    return resourceAssembler.toFullResource(dislike); 
} 

@ResponseBody 
@RequestMapping(value = "/posts/{id}/likes", method = RequestMethod.GET) 
public ResponseEntity<PagedResources<PersistentEntityResource>> getLikes(HttpRequest request, 
                     Pageable pageable, 
                     PersistentEntityResourceAssembler resourceAssembler) { 
    Page<Likey> page = likeRepository.findAll(pageable); 
    return ResponseEntity 
      .ok() 
      .cacheControl(CacheControl.maxAge(5, TimeUnit.SECONDS)) 
      .eTag(String.valueOf(page.hashCode())) 
      .body(pagedResourcesAssembler.toResource(page, resourceAssembler)); 

} 

@ResponseBody 
@RequestMapping(value = "/posts/{id}/dislikes", method = RequestMethod.GET) 
public ResponseEntity<PagedResources<PersistentEntityResource>> getDislikes(HttpRequest request, 
                     Pageable pageable, 
                     PersistentEntityResourceAssembler resourceAssembler) { 
    Page<DisLike> page = dislikeRepository.findAll(pageable); 
    return ResponseEntity 
      .ok() 
      .cacheControl(CacheControl.maxAge(5, TimeUnit.SECONDS)) 
      .eTag(String.valueOf(page.hashCode())) 
      .body(pagedResourcesAssembler.toResource(page, resourceAssembler)); 

} 

私がここで間違っていることを誰もが知っていますか?

編集:私は特に、以前のプリフライト問題を回避するためにここにOPTIONS要求を認証する必要がありましたように私WebSecurityConfig.javaはここでは関係あるかもしれないならば、私も思ったんだけど:

@Configuration 
@EnableWebSecurity 
@EnableAutoConfiguration 
@EnableGlobalMethodSecurity(prePostEnabled = true) 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 

@Autowired 
private JwtAuthenticationEntryPoint unauthorizedHandler; 

@Autowired 
private JwtAuthenticationProvider authenticationProvider; 

@Bean 
@Override 
public AuthenticationManager authenticationManager() throws Exception { 

    return new ProviderManager(Arrays.asList(authenticationProvider)); 
} 

@Bean 
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception { 
    JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter(); 
    authenticationTokenFilter.setAuthenticationManager(authenticationManager()); 
    authenticationTokenFilter.setAuthenticationSuccessHandler(new JwtAuthenticationSuccessHandler()); 
    return authenticationTokenFilter; 
} 

@Override 
protected void configure(HttpSecurity httpSecurity) throws Exception { 
    httpSecurity 
      // we don't need CSRF because our token is invulnerable 
      .csrf().disable() 
      // All urls must be authenticated (filter for token always fires (/**) 
      .authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").authenticated() 
      .and() 
      // Call our errorHandler if authentication/authorisation fails 
      .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) 
      .and() 
      // don't create session 
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); //.and() 
    // Custom JWT based security filter 
    httpSecurity 
      .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); 

    // disable page caching 
    httpSecurity.headers().cacheControl(); 

} 

}

答えて

0

あなたがしなければなりませんあなたのSpringコードは応答ヘッダとしてAccess-Control-Expose-Headersを送ってください - 私が見る限り、あなたがすでに持っているコードはconfig.addExposedHeader(…)です。しかし、あなたが応答のAccess-Control-Expose-Headersヘッダを見ていないなら、私は設定コードが期待どおりに動いていないと思って、それをデバッグする必要があります。

Angular2 Http Response missing header key/valuesからの提案は、それはちょうどあなたのクライアント側のフロントエンドコードから送信される要求に追加取得するAccess-Control-Expose-Headers要求にヘッダを引き起こしているので、その提案が間違っているheaders.append('Access-Control-Expose-Headers', 'etag');

を追加しました。

しかし、Access-Control-Expose-Headersは、レスポンスヘッダーであり、リクエストを送信しているサーバーがレスポンスで送信する必要があります。

私はこれを試してみましたが、私は次のようerrror取得:「リクエストヘッダフィールドAccess-Control-Expose-Headersは、プリフライト応じてAccess-Control-Allow-Headersによって許可されていません」

これは、クライアント側のフロントエンドコードがそのヘッダーを送信してはならないからです。

+0

私は現在、この問題をデバッグする機会はありませんでしたが、これらのヘッダーのいくつかの目的や、春のCORS設定の構成に関する私の疑惑の多くを確認しました。私はCORSを取り巻く多くの問題を抱えていました。これは主に、スプリング・ツー・セキュリティとクロス・オリジン・サポートの組み合わせによるものでした。ありがとう、私は今夜これを調べます。 – gezinspace

関連する問題