Solve cross-domain issues by setting response headers

Many articles on the Internet tell you that adding these response headers directly to Nginx can solve cross-domain problems. Of course, most cases can be solved, but I believe there are still many cases where cross-domain problems will still be reported even if the configuration is correct. .
This is most likely because the server did not correctly handle the preflight request, which is the OPTIONS request.
CORS request will add an HTTP query request before formal communication, called a “preflight” request (preflight); the browser first asks the server whether the domain name of the current web page is in the server’s permission list, and Which HTTP verbs and header fields can be used. Only when a positive reply is received will the browser issue a formal XMLHttpRequest request, otherwise an error will be reported.
Cross-domain mainly involves 4 response headers:

  • Access-Control-Allow-Origin is used to set the source address that allows cross-domain requests (preflight requests and formal requests will be verified when cross-domain)
  • Access-Control-Allow-Headers Special header information fields allowed to be carried across domains (only verified in preflight requests)
  • Access-Control-Allow-Methods cross-domain allowed request methods or HTTP verbs (only in preflight request verification)
  • Access-Control-Allow-Credentials Whether to allow the use of cookies across domains. If you want to use cookies across domains, you can add this request response header and set the value to true (setting it or not setting it will not affect the request sending, but will only affect the Whether cookies need to be carried across domains, but if set, both preflight requests and formal requests need to be set). However, it is not recommended to use it cross-domain (it has been used in the project, but it is unstable and cannot be carried by some browsers) unless necessary, because there are many alternatives.

What is a preflight request? : When a cross-domain condition occurs, the browser first asks the server whether the domain name of the current web page is in the server’s permission list, and which HTTP verbs and header information fields can be used. Only when a positive reply is received will the browser issue a formal XMLHttpRequest request, otherwise an error will be reported. As shown below

This request is an **OPTIONSrequest, so if the server wants to support cross-domain requests, it needs to support theOPTIONS** request.
Then using nginx can fully support cross-domain,

nginx solves cross-domain issues

Use nginx to set up response headers and process OPTIONS requests
Option 1 *: Wildcard, all allowed, has security risks (not recommended).
Once this method is enabled, it means that any domain name can directly make cross-domain requests:

 1 server {<!-- -->
  2         ...
  3 location / {<!-- -->
  4 # Allow all headers, all fields, all methods
  5 add_header 'Access-Control-Allow-Origin' '*';
  6 add_header 'Access-Control-Allow-Headers' '*';
  7 add_header 'Access-Control-Allow-Methods' '*';
  8 # OPTIONS Return directly to 204h
  9 if ($request_method = 'OPTIONS') {<!-- -->
 10 return 204;
 11 }
 12}
 13...
 14}

Solution 2: Multiple domain name configuration (recommended)
Configure multiple domain names in the map. Only the configured ones are allowed to cross domains:

 1 map $http_origin $corsHost {<!-- -->
  2 default 0;
  3 "~https://zzzmh.cn" https://zzzmh.cn;
  4 "~https://chrome.zzzmh.cn" https://chrome.zzzmh.cn;
  5 "~https://bz.zzzmh.cn" https://bz.zzzmh.cn;
  6}
  7 server {<!-- -->
  8         ...
  9 location / {<!-- -->
 10 # Allow all headers, all $corsHost fields, all methods
 11 add_header 'Access-Control-Allow-Origin' $corsHost;
 12 add_header 'Access-Control-Allow-Headers' '*';
 13 add_header 'Access-Control-Allow-Methods' '*';
 14 # OPTIONS directly returns 204
 15 if ($request_method = 'OPTIONS') {<!-- -->
 16 return 204;
 17}
 18}
 19...
 20}

Through nginx configuration, cross-domain solutions can be easily solved. OPTIONS pre-requests are also completed by nginx. The back-end server does not need to care about this issue

Backend solution

Set up response headers and handle OPTIONS requests through our backend server
_Then the question is, the backend needs to process the preflight request separately, so do we still need to write a separate interface for development? . There is no need to worry, because springMvc has already taken care of it for us.
_The specific implementation is as follows

private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {<!-- -->

@Nullable
private final CorsConfiguration config;

public PreFlightHandler(@Nullable CorsConfiguration config) {<!-- -->
this.config = config;
}

@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {<!-- -->
corsProcessor.processRequest(this.config, request, response);
}

@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {<!-- -->
return this.config;
}
}
public class DefaultCorsProcessor implements CorsProcessor {<!-- -->

private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class);


@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {<!-- -->

Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY);
if (!varyHeaders.contains(HttpHeaders.ORIGIN)) {<!-- -->
response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
}
if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) {<!-- -->
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
}
if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) {<!-- -->
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
}

if (!CorsUtils.isCorsRequest(request)) {<!-- -->
return true;
}

if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {<!-- -->
logger.trace("Skip: response already contains "Access-Control-Allow-Origin"");
return true;
}

boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {<!-- -->
if (preFlightRequest) {<!-- -->
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
else {<!-- -->
return true;
}
}

return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {<!-- -->

String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();

if (allowOrigin == null) {<!-- -->
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}

HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
if (allowMethods == null) {<!-- -->
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
rejectRequest(response);
return false;
}

List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
List<String> allowHeaders = checkHeaders(config, requestHeaders);
if (preFlightRequest & amp; & amp; allowHeaders == null) {<!-- -->
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
rejectRequest(response);
return false;
}

responseHeaders.setAccessControlAllowOrigin(allowOrigin);

if (preFlightRequest) {<!-- -->
responseHeaders.setAccessControlAllowMethods(allowMethods);
}

if (preFlightRequest & amp; & amp; !allowHeaders.isEmpty()) {<!-- -->
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}

if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {<!-- -->
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}

if (Boolean.TRUE.equals(config.getAllowCredentials())) {<!-- -->
responseHeaders.setAccessControlAllowCredentials(true);
}

if (preFlightRequest & amp; & amp; config.getMaxAge() != null) {<!-- -->
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}

response.flush();
return true;
}

springMvc will handle cross-domain requests based on cross-domain configuration.

Configuring cross-domain

 @Override
    public void addCorsMappings(CorsRegistry registry) {<!-- -->
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH");
    }

You can configure the above CorsConfiguration through the configuration class
However, the new version of springMvc has prohibited simultaneous settings.
allowCredentials is true
allowCredentials is false
If you have this requirement, you need to write your own interceptor to handle it.

/ Cross-domain interceptor
public class CrosInterceptor implements HandlerInterceptor {<!-- -->
    //Intercept before accessing
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {<!-- -->
      //Set response headers
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");


        return true;
    }
}

Configure interceptor

 @Override
    public void addInterceptors(InterceptorRegistry registry) {<!-- -->
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**"); // Intercept all requests and determine whether login is required by judging whether there is @LoginRequired annotation

        registry.addInterceptor(reqIdHandler())
                .addPathPatterns("/**"); //Verify whether the corpId is consistent

        registry.addInterceptor(new CrosInterceptor())
                .addPathPatterns("/**");
    }

Interceptor skips OPTIONS request

However, you need to pay attention to prevent the server’s interceptor from intercepting the OPTIONS request, causing the preflight to fail and reporting a cross-domain error
For example, this will lead to cross-domain errors.

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {<!-- -->
String token = request.getHeader(Constants.TOKEN);

\t
\t
if (StrUtil.isEmpty(token)) {<!-- -->
throw new TokenException("Token is empty");
}

    }

Judgment should be added to the interceptor

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {<!-- -->
if("OPTIONS".equals(request.getMethod())){<!-- -->
return true;
}
\t
        String token = request.getHeader(Constants.TOKEN);
       
if (StrUtil.isEmpty(token)) {<!-- -->
throw new TokenException("Token is empty");
}

    }
syntaxbug.com © 2021 All Rights Reserved.