跨域
背景
浏览器访问前端,前端访问后端不同的服务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进一步封装