跨域二三事之options请求

背景

今天小伙伴问了一个比较奇怪的问题 在某个子系统中通过跨域请求发现POST请求无法正常拿到cookie导致报错

我们从代码来分析一下原因

思路

最重要的永远是碰到问题解决问题的思路。首先碰到xhr没有携带cookie导致重定向到cas登录页的问题之前碰到过

跨域二三事之withCredentials

那么小伙伴也确定是加了对应的withCredentials 并且get请求可以正常返回。

那么缘何出现了POST请求不成功呢?

知识点

我们知道使用cors做跨域请求是分两种的。

  • 简单请求
  • 非简单请求

The term user credentials for the purposes of this specification means cookies, HTTP authentication, and client-side SSL certificates that would be sent based on the user agent’s previous interactions with the origin. Specifically it does not refer to proxy authentication or the Origin header. [COOKIES] The term cross-origin is used to mean non same origin. A method is said to be a simple method if it is a case-sensitive match for one of the following:

GET

HEAD

POST

To protect resources against cross-origin requests that could not originate from certain user agents before this specification existed a preflight request is made to ensure that the resource is aware of this specification. The result of this request is stored in a preflight result cache. The steps below describe what user agents must do for a cross-origin request with preflight. This is a request to a non same-origin URL that first needs to be authorized using either a preflight result cacheentry or a preflight request.

Go to the next step if the following conditions are true:

For request method there either is a method cache match or it is a simple method and the force preflight flag is unset.

For every header of author request headers there either is a header cache match for the field name or it is a simple header.

Otherwise, make a preflight request. Fetch the request URL from originsource origin using referrer source as override referrer source with the manual redirect flag and the block cookies flag set, using the method OPTIONS, and with the following additional constraints:

Include an Access-Control-Request-Method header with as header field value the request method (even when that is a simple method).

If author request headers is not empty include an Access-Control-Request-Headers header with as header field value a comma-separated list of the header field names from author request headers in lexicographical order, each converted to ASCII lowercase (even when one or more are a simple header).

Exclude the author request headers.

Exclude user credentials.

Exclude the request entity body.

这里的preflight也就是我们经常说的options请求【需要注意简单请求的命中是大小写敏感的】

但是options具有一个特点在 发送时 会排除author header,user credentials,request body

这样自然不会发送cookie

解析

之前的方案在做跨域并未使用filter做【filter的生命周期各位学习servlet应该很熟悉】而是采用的Spring interceptor

我们在配置跨域的时候springboot提供了较好的方案

@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**");
}

spring的cors使用了

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.springframework.web.cors;
 
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
 
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.WebUtils;
 
/**
 * The default implementation of {@link CorsProcessor}, as defined by the
 * <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>.
 *
 * <p>Note that when input {@link CorsConfiguration} is {@code null}, this
 * implementation does not reject simple or actual requests outright but simply
 * avoid adding CORS headers to the response. CORS processing is also skipped
 * if the response already contains CORS headers, or if the request is detected
 * as a same-origin one.
 *
 * @author Sebastien Deleuze
 * @author Rossen Stoyanchev
 * @since 4.2
 */
public class DefaultCorsProcessor implements CorsProcessor {
 
   private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
 
   private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class);
 
 
   @Override
   @SuppressWarnings("resource")
   public boolean processRequest(CorsConfiguration config, HttpServletRequest request, HttpServletResponse response)
         throws IOException {
 
      if (!CorsUtils.isCorsRequest(request)) {
         return true;
      }
 
      ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
      if (responseHasCors(serverResponse)) {
         logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");
         return true;
      }
 
      ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
      if (WebUtils.isSameOrigin(serverRequest)) {
         logger.debug("Skip CORS processing: request is from same origin");
         return true;
      }
 
      boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
      if (config == null) {
         if (preFlightRequest) {
            rejectRequest(serverResponse);
            return false;
         }
         else {
            return true;
         }
      }
 
      return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
   }
 
   private boolean responseHasCors(ServerHttpResponse response) {
      try {
         return (response.getHeaders().getAccessControlAllowOrigin() != null);
      }
      catch (NullPointerException npe) {
         // SPR-11919 and https://issues.jboss.org/browse/WFLY-3474
         return false;
      }
   }
 
   /**
    * Invoked when one of the CORS checks failed.
    * The default implementation sets the response status to 403 and writes
    * "Invalid CORS request" to the response.
    */
   protected void rejectRequest(ServerHttpResponse response) throws IOException {
      response.setStatusCode(HttpStatus.FORBIDDEN);
      response.getBody().write("Invalid CORS request".getBytes(UTF8_CHARSET));
   }
 
   /**
    * Handle the given request.
    */
   protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
         CorsConfiguration config, boolean preFlightRequest) throws IOException {
 
      String requestOrigin = request.getHeaders().getOrigin();
      String allowOrigin = checkOrigin(config, requestOrigin);
 
      HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
      List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
 
      List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
      List<String> allowHeaders = checkHeaders(config, requestHeaders);
 
      if (allowOrigin == null || allowMethods == null || (preFlightRequest && allowHeaders == null)) {
         rejectRequest(response);
         return false;
      }
 
      HttpHeaders responseHeaders = response.getHeaders();
      responseHeaders.setAccessControlAllowOrigin(allowOrigin);
      responseHeaders.add(HttpHeaders.VARY, HttpHeaders.ORIGIN);
 
      if (preFlightRequest) {
         responseHeaders.setAccessControlAllowMethods(allowMethods);
      }
 
      if (preFlightRequest && !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 && config.getMaxAge() != null) {
         responseHeaders.setAccessControlMaxAge(config.getMaxAge());
      }
 
      response.flush();
      return true;
   }
 
   /**
    * Check the origin and determine the origin for the response. The default
    * implementation simply delegates to
    * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
    */
   protected String checkOrigin(CorsConfiguration config, String requestOrigin) {
      return config.checkOrigin(requestOrigin);
   }
 
   /**
    * Check the HTTP method and determine the methods for the response of a
    * pre-flight request. The default implementation simply delegates to
    * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
    */
   protected List<HttpMethod> checkMethods(CorsConfiguration config, HttpMethod requestMethod) {
      return config.checkHttpMethod(requestMethod);
   }
 
   private HttpMethod getMethodToUse(ServerHttpRequest request, boolean isPreFlight) {
      return (isPreFlight ? request.getHeaders().getAccessControlRequestMethod() : request.getMethod());
   }
 
   /**
    * Check the headers and determine the headers for the response of a
    * pre-flight request. The default implementation simply delegates to
    * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
    */
   protected List<String> checkHeaders(CorsConfiguration config, List<String> requestHeaders) {
      return config.checkHeaders(requestHeaders);
   }
 
   private List<String> getHeadersToUse(ServerHttpRequest request, boolean isPreFlight) {
      HttpHeaders headers = request.getHeaders();
      return (isPreFlight ? headers.getAccessControlRequestHeaders() : new ArrayList<String>(headers.keySet()));
   }
 
}
/**
 * Utility class for CORS request handling based on the
 * <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>.
 *
 * @author Sebastien Deleuze
 * @since 4.2
 */
public abstract class CorsUtils {
 
   /**
    * Returns {@code true} if the request is a valid CORS one.
    */
   public static boolean isCorsRequest(HttpServletRequest request) {
      return (request.getHeader(HttpHeaders.ORIGIN) != null);
   }
 
   /**
    * Returns {@code true} if the request is a valid CORS pre-flight one.
    */
   public static boolean isPreFlightRequest(HttpServletRequest request) {
      return (isCorsRequest(request) && HttpMethod.OPTIONS.matches(request.getMethod()) &&
            request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
   }
 
}

看到在PreFlightRequest的请求时会返回response

但是当我们使用casFilter的时候 由于没有帮我们处理Options请求 在收到Options请求的时候已经判断cookie没有携带 自然导致重定向到cas画面

也可以参考 https://stackoverflow.com/questions/10133497/cors-withcredentials-xhr-preflight-not-posting-cookies-in-firefox

猜你喜欢

转载自blog.csdn.net/u012501054/article/details/84617896