Cross-source request filters out most of my API response headers

I have a Spring Boot REST API with an Angular 4 interface. I'm generally very pleased with both of these frameworks. A recurring problem is related to CORS requests. It is like a mole kick game. Each time I give out a question, another quickly appears and destroys the weekend. I can make requests to my Spring boot rest api now without any problems. But ... when I want to get my headers from a response on my Angular website, only 5 headers are available, and most of them are missing, including the ETag header, which is currently of most concern. I read a SO post stating that I just need to add the request header to my Angular http call to open the header I need (by the way ... in the debug console, I see all the headers I expect). sentenceAngular2 Invalid Http header / header value was adding headers.append('Access-Control-Expose-Headers', 'etag' );

I tried this, but I received the following error message: "Request header field. Access-Control-Expose-Headers not allowed by Access-Control-Allow-Headers in the preflight response."

I am confused by this post, to be honest. I changed some of my CORS settings in Spring boot, but to no avail.

I have no idea where to go with this. I am almost considering reverting to PHP (cringe) from java + Spring boot, since I never had nightmares like this, I could not solve it with PHP.

Please help me if you have any suggestions.

The corresponding code for my Angular front end is below:

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

}
Run codeHide result

And the application class (with cors configuration) from the Spring boot application is given below:

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

And my controller:

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

}

}

- , ?

: , WebSecurityConfig.java , OPTIONS, :

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

}

}

+4
1

Spring send Access-Control-Expose-Headers , , , - , config.addExposedHeader(…). Access-Control-Expose-Headers , , , .

Angular2 / Http headers.append( "Access-Control-Expose-Headers", 'ETag');

, Access-Control-Expose-Headers , .

Access-Control-Expose-Headers - , , , .

, : " Access-Control-Expose-Headers Access-Control-Allow-Headers ".

, , .

0

Source: https://habr.com/ru/post/1678287/


All Articles