【问题标题】:WebMvcConfigurer addCorsMappings exposed headers doesn't workWebMvcConfigurer addCorsMappings 暴露的标头不起作用
【发布时间】:2021-03-13 13:22:53
【问题描述】:

我想返回一个 ETag 标头,但我的客户端无法读取它,因为它没有公开。 我有以下代码:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(@NonNull CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedHeaders("*")
                .allowedMethods("*")
                .allowedOrigins("*")
                .exposedHeaders("ETag");
    }

    @Bean
    public ShallowEtagHeaderFilter shallowEtagHeaderFilter() {
        return new ShallowEtagHeaderFilter();
    }
}

但客户端仍然无法读取 ETag。唯一有效的是:

    @ApiResponses({
            @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 500, message = "System Error")
    })
    @ApiOperation(value = "returns the meals", response = MealDTO.class, responseContainer = "List", produces = MediaType.APPLICATION_JSON_VALUE)
    @GetMapping("/meal")
    public List<MealDTO> getMeals(
            @ApiParam(name = "page", type = "Integer", value = "Number of the page", example = "2")
            @RequestParam(required = false) Integer page,
            @ApiParam(name = "size", type = "Integer", value = "The size of one page", example = "5")
            @RequestParam(required = false) Integer size,
            @ApiParam(name = "sortBy", type = "String", value = "sort criteria", example = "name.asc,price.desc")
            @RequestParam(required = false) String sortBy,
            @ApiParam(name = "userId", type = "long", value = "ID of the User", example = "-1")
            @PathVariable Long userId,
            HttpServletResponse response
    ) {
        log.debug("Entered class = MealController & method = getMeals");
        response.setHeader("Access-Control-Expose-Headers", "ETag");
        return this.mealService.getMealsByUserId(page, size, sortBy, userId);
    }

手动设置每个端点的公开标头。这不是 Cors Mapping 应该做的吗? ExposedHeaders 根本不适合我。

更新:

从下面的评论中我看到它可能与 WebSecurityConfigurerAdapter 有关,我还要补充一点:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private final UserDetailsService userDetailsService;

    public WebSecurityConfig(@Qualifier("userServiceImpl") UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .cors()
                .and()
                .addFilter(new AuthenticationFilter(authenticationManager()))
                .addFilter(new AuthorizationFilter(authenticationManager()))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

//        http.headers().cacheControl().disable();
    }

    //region Beans
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    //endregion
}

更新 2:

显示我如何从客户那里拨打电话:

export const getMealByIdApi: (mealId: number, etag: string | null) => Promise<Meal | void> = (mealId, etag) => {
    let _config = config;
    if (etag) {
        _config.headers['If-None-Match'] = etag;
    }

    return axiosInstance
        .get<Meal>(`/meal/${mealId}`, _config)
        .then(response => {
            log(`[getMealByIdApi] [${response.status}] Successful API call for meal with id ${mealId}.`);
            const result: Meal = response.data;
            result.etag = response.headers['etag'];
            return result;
        })
        .catch(err => {
            if (err.response.status === 304) {
                log(`[getMealByIdApi] [304] Successful API call for meal with id ${mealId}.`);
                return;
            }

            throw err;
        });
}

如果我没有在端点中明确指定,我在标头中没有 etag: response.setHeader("Access-Control-Expose-Headers", "ETag");

【问题讨论】:

  • @samabcde 很抱歉回复晚了,我现在才开始再次处理这个问题。我研究了一下,它似乎对我不起作用。
  • 我可以使用类似的配置在 http 响应标头中看到 Etag,但是对于 userDetailsService(无法注入)、configure(HttpSecurity http) (@987654329),我无法按照您在 WebSecurityConfig 中的代码@, new AuthorizationFilter(authenticationManager())) 无法编译
  • @samabcde 这些是一些用于授权和身份验证的安全过滤器。我认为它们与问题无关。你用邮递员测试过吗?如果是这样,我也这样做了,确实可以在那里看到它,但是如果我没有在端点中准确指定它,我的 Web 客户端将看不到 ETag。
  • 经过一番调查,我认为不同的是响应头:“Access-Control-Expose-Headers:ETag”是否存在,当您添加response.setHeader("Access-Control-Expose-Headers", "ETag");时,此头将始终存在于响应中,否则只有当请求是CORS时才会存在(请求头:“origin”存在并且与服务器不同),希望对您有所帮助。

标签: java spring spring-boot cors


【解决方案1】:

根据Axios get access to response header fields。我们需要将“ETag”添加到“Access-Control-Expose-Headers”,这样我们就可以访问response.headers['etag']。 由于您已经添加了ShallowEtagHeaderFilter,并且还在addCorsMappings 中暴露了“etag”。应将“ETag”添加到 CORS 请求的响应标头“Access-Control-Expose-Headers”中。

为了确保您请求的是 CORS,您可以调试 DefaultCorsProcessor#processRequest 方法,并检查 CorsUtils.isCorsRequest(request) 是否返回 true。

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
        HttpServletResponse response) throws IOException {

    response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
    response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
    response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);

    if (!CorsUtils.isCorsRequest(request)) {
        return true;  // will not add response header if not CORS
    }
     
    ...add response header

如果返回false,可以从CorsUtils#isCorsRequest描述中查看CORS请求的需求:

如果请求是有效的 CORS,则返回 true,方法是检查 Origin 标头是否存在并确保来源不同。

【讨论】:

    猜你喜欢
    • 2021-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 2019-02-01
    • 2018-06-19
    • 1970-01-01
    • 2019-11-25
    相关资源
    最近更新 更多