使用 spring 和 docker 引发运行时异常时出现 cors 问题

Posted

技术标签:

【中文标题】使用 spring 和 docker 引发运行时异常时出现 cors 问题【英文标题】:cors issue when throwing a runtime exception with spring and docker 【发布时间】:2020-07-17 12:36:14 【问题描述】:

我使用 spring、angular 和 docker 构建了一个 Web 应用程序。我目前也有两个环境。一个开发环境,其中使用npm start 提供角度,使用mvn spring-boot:run 提供弹簧。还有一个测试环境,我使用 digitalocean 来托管我的应用程序并使用 docker 容器化 angular 和 spring 应用程序。

在这个应用程序中,我有一个注册页面,用户可以在其中注册和一个弹簧控制器来处理注册请求。这是我的控制器的代码:

@PostMapping("/register")
public SaveUserDto register(@RequestBody @Valid SaveUserDto saveUserDto, WebRequest request) 

        User userToSave = this.modelMapper.map(saveUserDto, User.class);
        User registeredUser = null;
        try 
            registeredUser = this.registrationService.register(userToSave);
         catch (DataIntegrityViolationException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
            if(e.getRootCause().toString().contains("uk_pfki8mu6sefesty9h30d4n3yv")) 
                throw new EmailAlreadyExistException();
            
        
        try 
            eventPublisher.publishEvent(new OnRegistrationCompleteEvent(registeredUser, request.getLocale(), request.getHeader("Origin")));
         catch (Exception me) 
            System.out.println(me.getMessage());
        

        if(registeredUser != null) 
            return this.modelMapper.map(registeredUser, SaveUserDto.class);
         else 
            return null;
        

由于我的 angular 是从 host:4200 提供的(其中主机是我的开发环境中的 localhsot,或者我的测试环境中的 165.22.72.253)并且我的 api 是从 host:8761 提供的,所以我有一个 cors 配置来启用 cors 请求.这是配置:

@Bean
public CorsFilter corsFilter() 
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://" + clientHost + ":4200");
        config.addAllowedOrigin("http://" + clientHost + ":80");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("DELETE");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);

这里的 clientHost 是一个参数,在开发环境中运行时值为 localhost,在我的测试环境中为 165.22.72.253。对于大多数用例,此设置有效,但在特定情况下会失败。

当用户尝试使用现有电子邮件地址注册时,就会出现问题。然后你可以从控制器方法中看到我抛出了一个 EmailAlreadyExistException。在开发环境中,一切正常,即请求返回错误代码 500,并显示消息“电子邮件已存在”。但是在测试环境中我得到Access to XMLHttpRequest at 'http://165.22.72.253:8762/register' from origin 'http://165.22.72.253:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

如果这里有用的话是我的 docker-compose 文件:

version: "3.5"

services:

  tomcat-file:
    container_name: tomcat-file-server
    image: tomcat-file-server
    build:
      context: .
      dockerfile: tomcat-file.prod.dockerfile
    volumes:
      - /home/vetter_leo/medias:/usr/local/tomcat/src
    ports:
      - "8205:8205"

networks:
  default:
    external:
      name: helenos-network

此时我不明白为什么它在开发环境中工作而不是在测试环境中。任何帮助将不胜感激。提前致谢。

【问题讨论】:

@Veteouz 测试环境中的请求通过的原始标头是什么? 你能追加吗?在测试环境中生成的原始格式的 HTTP 请求消息 源头是“165.22.72.253:4200”,原始请求是“POST /register HTTP/1.1 Host: 165.22.72.253:8762 Connection: keep-alive Content-Length: 107 Accept: application/ json, text/plain, / Authorization: Bearer null User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/80.0.3987.149 Safari/ 537.36 Content-Type:application/json 来源:165.22.72.253:4200Referer:165.22.72.253:4200/account/sign-up Accept-Encoding:gzip,deflate Accept-Language:fr-FR,fr;q=0.9,en-US;q=0.8,en;q =0.7,de;q=0.6,fi;q=0.5" 收到来自同一服务器的任何请求的 http 响应 @SamThomas Access-Control-Allow-Origin 应该是响应标头的一部分。检查fr.wikipedia.org/wiki/…。 【参考方案1】:

我最终要做的是向控制器中的异常添加一个处理程序,该处理程序向我返回正确的消息:这样:

@ExceptionHandler( EmailAlreadyExistException.class )
public String handleException() 
    return "\"error\": \"" + messages.getMessage("emailAlreadyExist", null, Locale.ENGLISH) + "\"";

【讨论】:

【参考方案2】:

您是否尝试代理 API 调用?在 Angular 项目中,您可以创建一个proxy.conf.json 文件并添加以下配置。


  "/api": 
    "target": "http://localhost:3000",
    "secure": false
  

然后在package.json改开始,

"start": "ng serve --proxy-config proxy.conf.json"

如果您想从 psring 端执行此操作,请将 @CrossOrigin 添加到控制器。

但是如果你认为你会在生产环境中遇到同样的问题,我的建议是使用像 nginx 这样的反向代理。您是 Angular 应用程序,API 通过相同的端口提供服务,但所有 API 都以 api 为前缀。在 Nginx 方面,您可以使用 api 前缀过滤 API 调用并将它们代理到后端服务。

【讨论】:

我不想使用代理。这个问题应该由服务器处理 那么你可以使用“@CrossOrigin”注解。【参考方案3】:

Spring boot 使用默认的错误控制器。您可以提供自己的并在响应中添加 Access-Control-Allow-Credentials 标头:

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
public class CustomErrorController implements ErrorController 

    @RequestMapping("/error")
    public String handleErrors(HttpServletRequest request, HttpServletResponse response) 
        response.addHeader("Access-Control-Allow-Credentials","true");
        String errorMessage = request.getAttribute(RequestDispatcher.ERROR_MESSAGE).toString();

        return errorMessage;
    

    @Override
    public String getErrorPath() 
        return "/error";
    

【讨论】:

【参考方案4】:

由于它在托管在 DigitalOcean 上的测试环境中出错,您是否探索过他们是否有办法在默认情况下覆盖 CORS 策略? https://www.digitalocean.com/docs/spaces/how-to/configure-cors/

【讨论】:

【参考方案5】:

可能是在调用cors过滤器之前抛出异常的问题?

你可以试试这个吗?

  //...

  source.registerCorsConfiguration("/**", config);
  FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
  bean.setOrder(Ordered.HIGHEST_PRECEDENCE);

  return bean;

【讨论】:

以上是关于使用 spring 和 docker 引发运行时异常时出现 cors 问题的主要内容,如果未能解决你的问题,请参考以下文章