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