跨域请求理解

背景

http://localhost:8080
http://localhost:5000
http://localhost:6000
浏览器
VUE项目
用户
订单
结束

浏览器访问前端,前端访问后端不同的服务http://localhost:8080 -> http://localhost:5000
前端访问后端接口、两个请求不同源页面抛出跨域异常

同源策略

如果两个 URL 的 协议、主机、端口 都相同的话,则这两个 URL 是同源、反之则浏览器视为非同源(跨域:违背了同源策略

若请求为跨域请求浏览器会发送一个预请求验证服务器配置

预请求的目的是让服务器知道浏览器打算执行一个跨域请求,并询问服务器是否允许这样的请求。这个预请求是一个HTTP OPTIONS 请求,它包含了一些特殊的头部字段,如 Access-Control-Request-Method Access-Control-Request-Headers,这些字段告诉服务器实际请求将使用哪种HTTP方法和哪些自定义头部。

服务器收到预请求后,会根据其CORS配置来决定是否允许这个跨域请求。如果允许,服务器会在响应中包含适当的CORS头部,如 Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers。这些头部告诉浏览器服务器允许哪些源、哪些HTTP方法和哪些头部进行跨域请求。

如果服务器允许预请求,浏览器会发送实际的跨域请求。如果服务器不允许预请求,或者没有在响应中设置正确的CORS头部,浏览器将不会发送实际的跨域请求,并在控制台中显示一个错误消息,表明跨域请求被阻止。

跨域解决

1、cors

通过后端对增加响应的响应头信息、
如果你的应用是一个标准的 Java EE 应用,
确保你的 web.xml 文件没有定义与 @WebFilter 注解冲突的过滤器配置。
如果你使用的是 Spring Boot,可能需要额外的配置来启用 @WebFilter。

Spring Boot 默认不扫描 Servlet API 的注解,因此你可能需要添加 @ServletComponentScan 注解到你的主配置类



import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

@WebFilter(filterName = "corsFilter", urlPatterns = "/*",
        initParams = {@WebInitParam(name = "allowOrigin", value = "*"),
                @WebInitParam(name = "allowMethods", value = "GET,POST,PUT,DELETE,OPTIONS"),
                @WebInitParam(name = "allowCredentials", value = "true"),
                @WebInitParam(name = "allowHeaders", value = "*")})
public class CorsFilter implements Filter {

    private String allowOrigin;
    private String allowMethods;
    private String allowCredentials;
    private String allowHeaders;
    private String exposeHeaders;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        allowOrigin = filterConfig.getInitParameter("allowOrigin");
        allowMethods = filterConfig.getInitParameter("allowMethods");
        allowCredentials = filterConfig.getInitParameter("allowCredentials");
        allowHeaders = filterConfig.getInitParameter("allowHeaders");
        exposeHeaders = filterConfig.getInitParameter("exposeHeaders");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (!StringUtils.isEmpty(allowOrigin)) {
            if(allowOrigin.equals("*")){
                // 设置哪个源可以访问
                String origin = request.getHeader("Origin");
                System.out.println("Origin- > "+origin);
                response.setHeader("Access-Control-Allow-Origin", origin);
            }else{
                List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
                if (allowOriginList != null && allowOriginList.size() > 0) {
                    String currentOrigin = request.getHeader("Origin");
                    if (allowOriginList.contains(currentOrigin)) {
                        response.setHeader("Access-Control-Allow-Origin", currentOrigin);
                    }
                }
            }
        }
        if (!StringUtils.isEmpty(allowMethods)) {
            //设置哪个方法可以访问
            response.setHeader("Access-Control-Allow-Methods", allowMethods);
        }
        if (!StringUtils.isEmpty(allowCredentials)) {
            // 允许携带cookie
            response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
        }
        if (!StringUtils.isEmpty(allowHeaders)) {
            // 允许携带哪个头
            if(allowOrigin.equals("*")){
                String header = request.getHeader("Access-Control-Request-Headers");
                System.out.println("Allow-Headers- > "+header);
                response.setHeader("Access-Control-Allow-Headers", header);
            }else {
                response.setHeader("Access-Control-Allow-Headers", allowHeaders);
            }

        }
        if (!StringUtils.isEmpty(exposeHeaders)) {
            // 允许携带哪个头
            response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}
常见问题
1、‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’

Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
XMLHttpRequest对象的withCredentials属性是一个布尔值,用于指示跨站访问控制(Cross-Site Access Control)请求是否应该携带跨站点的访问控制凭据(credentials)。当withCredentials属性设置为true时,XMLHttpRequest会发送包括cookies、HTTP认证或客户端SSL证明等凭据的请求。

const axiosInstance = axios.create({  
  baseURL: 'https://api.example.com',  
  withCredentials: false, // 设置为false,不发送cookies  
  timeout: 1000,  
  headers: {  
    'Content-Type': 'application/json'  
  }  
});  

这个属性对于跨域请求(CORS)特别重要,因为它允许开发者控制是否将用户的身份验证信息(如cookies)发送给其他域。默认情况下,withCredentials是false,这意味着请求不会包含凭据。

在CORS请求中,如果withCredentials被设置为true,那么服务器必须在响应头中包含Access-Control-Allow-Origin和Access-Control-Allow-Credentials两个头部,并且Access-Control-Allow-Origin不能设置为通配符*。这是因为包含凭据的请求可能会暴露用户的敏感信息,因此服务器需要明确知道它信任哪些来源

动态获取来源并设置Access-Control-Allow-Origin
if(allowOrigin.equals("*")){
                // 设置哪个源可以访问
                String origin = request.getHeader("Origin");
                System.out.println("Origin- > "+origin);
                response.setHeader("Access-Control-Allow-Origin", origin);
            }
2、Request header field xxxx is not allowed by Access-Control-Allow-Headers

在CORS预检请求中,浏览器会发送一个带有 Access-Control-Request-Headers 头部的请求,列出实际请求将包含的所有头部字段。服务器必须检查这个头部,并在预检响应的 Access-Control-Allow-Headers 头部中包含所有这些头部字段,以表明它允许这些头部。

请确保您的服务器正确处理了 Access-Control-Request-Headers,并且在预检响应中包含了所有必需的头部。如果您不确定实际请求将包含哪些头部,您可以尝试暂时将 Access-Control-Allow-Headers 设置为 *,以允许所有头部,然后逐步缩小允许的范围,直到找到引起问题的特定头部。

动态获取相关允许的头部信息
// 允许携带哪个头
            if(allowOrigin.equals("*")){
                String header = request.getHeader("Access-Control-Request-Headers");
                System.out.println("Allow-Headers- > "+header);
                response.setHeader("Access-Control-Allow-Headers", header);
            }

2、jsonp

利用 script 标签的src属性没有跨域限制的漏洞,需要服务器响应数据为json格式,缺点是仅支持get方法具有局限性

3、代理服务器(vue-cli/nginx/Node)

vue.config.js 是一个可选的配置文件,下面使用vue-cli的配置完成代理服务器。如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。
module.exports = {
  devServer: {
    proxy: {
      '/api': {
     	//真实地址
        target: '<url>',
        //默认为true
        ws: true,
        //默认为true,是否伪装初始路径(true: 8080->5000 false: 8080->8080)
        changeOrigin: true
        //正则匹配路径重写 http://localhost:8080/api/list -> http://localhost:8080/list
        pathRewrite: {'^/api':''}
      },
      '/foo': {
        target: '<other_url>'
      }
    }
  }
}

知识衍生

跨域实际浏览器已经拿到了服务器响应的数据,受同源策略影响不能正常给浏览器使用、
访问请求的核心是封装 XMLHttpRequest.send(),JQuery的$.get(),Axios等都是对XMLHttpRequest进一步封装

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值