使用 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 问题的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring MVC 运行 Spring Boot 时引发空指针异常

docker运行Spring Cloud使用外部IP

无法使用 Spring 数据源参数运行 docker 容器

使用 Postgres docker 实例运行 Spring Boot docker 实例

使用 Spring Boot 代码运行 docker 容器

使用docker发布spring cloud应用