同源策略是一种限制性的跨域规范,它限制了网站与源域之外的资源进行交互的能力。
注意:协议、域名、端口号,只要其中一个不同,即:非同源,就会产生跨域问题!!
当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP
请求。比如,站点 http://www.a.com
的某 HTML
页面通过 img
的 src
请求 http://www.b.com/image.jpg
。网络上的许多页面都会加载来自不同域的 CSS
样式表,图像和脚本等资源。
出于安全原因,浏览器限制从页面脚本内发起的跨域请求,有些浏览器不会限制跨域请求的发起,但是会将结果拦截了。 这意味着使用这些 API 的 Web 应用程序只能加载同一个域下的资源,除非使用 CORS
机制(Cross-Origin Resource Sharing 跨源资源共享)获取目标服务器的授权来解决这个问题。
在同源策略的限制下,非同源的网站之间不能发送 ajax
请求。因此浏览器受同源策略的限制,不能正常对非同源的域发起操作请求,否则就会产生跨域问题。
前端:jsonp跨域
后台:spring mvc过滤器
nginx反向代理
CORS
......
为了解决浏览器同源问题,W3C
提出了跨源资源共享,即 CORS(Cross-Origin Resource Sharing)
。
CORS
做到了如下两点:
CORS
接口,就可以跨源通信简单请求与非简单请求
基于这两点,
CORS
将请求分为两类:简单请求和非简单请求(具体解释见参考部分阮一峰的网络日志《跨域资源共享 CORS 详解》
)。
HTTP
响应首部字段
Access-Control-Allow-Origin
:指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。Access-Control-Expose-Headers
:设置允许浏览器访问的reponse
头白名单。Access-Control-Allow-Methods
:指明了实际请求所允许使用的HTTP
方法。Access-Control-Max-Age
:指定了preflight
请求(预检请求)的结果能够被缓存的时长,单位是秒。Access-Control-Allow-Credentials
:指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。
HTTP
请求首部字段
Origin
:预检请求或实际请求的源站。Access-Control-Request-Method
:将实际请求所使用的 HTTP 方法告诉服务器。Access-Control-Request-Headers
:将实际请求所携带的首部字段告诉服务器。
gateway-cors:
mapping: /**
allowed_origins: http://localhost:9528,http://127.0.0.1:9528
allowed_methods: "*"
allowed_headers: "*"
max_age: 3611
allow_credentials: true
package com.abc.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author abc
* @version since 1.0.0
* @date 2022/8/17 10:17
* @apiNote 解决跨域问题
*/
@Configuration
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${gateway-cors.mapping:/**}")
private String mapping;
@Value("${gateway-cors.allowed_origins:*}")
private String[] allowedOrigins;
@Value("${gateway-cors.allowed_methods:*}")
private String[] allowedMethods;
@Value("${gateway-cors.allowed_headers:*}")
private String[] allowedHeaders;
@Value("${gateway-cors.max_age:3600}")
private Long maxAge;
@Value("${gateway-cors.allow_credentials:true}")
private Boolean allowCredentials;
@Override
public void addCorsMappings(CorsRegistry registry) {
//为指定的路径(模式)启用跨源请求处理。
//默认情况下,此映射的CORS配置采用CorsConfiguration.applyPermitDefaultValue()的配置
//即允许所有的origins(源),所有的headers(请求头字段),所有的GET、HEAD和POST方法。
registry.addMapping(mapping)
//设置允许访问该资源的外域源,这里是所有
// .allowedOrigins("*")
.allowedOrigins(allowedOrigins)
// .allowedOrigins("http://192.168.5.10:8088")
// .allowedOrigins("http://localhost:8088")
//设置实际请求所允许使用的 HTTP 方法。所有 -> *
.allowedMethods(allowedMethods)
//设置实际请求所允许携带的自定义首部字段。这里是所有
.allowedHeaders(allowedHeaders)
.maxAge(maxAge)
//设置是否允许请求中携带身份认证信息,比如Cookie。这里是允许
.allowCredentials(allowCredentials);
}
}
package com.abc.gateway.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
import java.util.Arrays;
/**
* @author abc
* @version since 1.0.0
* @date 2022/8/16 9:03
* @apiNote 过滤器配置跨域问题
*/
@Configuration
@Slf4j
public class CorsConfigure {
@Value("${gateway-cors.mapping:/**}")
private String mapping;
@Value("${gateway-cors.allowed_origins:*}")
private String[] allowedOrigins;
@Value("${gateway-cors.allowed_methods:*}")
private String[] allowedMethods;
@Value("${gateway-cors.allowed_headers:*}")
private String[] allowedHeaders;
@Value("${gateway-cors.max_age:3600}")
private Long maxAge;
@Value("${gateway-cors.allow_credentials:true}")
private Boolean allowCredentials;
@Bean
public CorsWebFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
CorsConfiguration cors = new CorsConfiguration();
//允许携带cookie
cors.setAllowCredentials(allowCredentials);
//允许任何来源
// cors.addAllowedOrigin("*");
cors.setAllowedOrigins(Arrays.asList(allowedOrigins));
//允许携带任何请求头
// cors.addAllowedHeader("*");
cors.setAllowedHeaders(Arrays.asList(allowedHeaders));
//允许任何HTTP方法
// cors.addAllowedMethod("*");
cors.setAllowedMethods(Arrays.asList(allowedMethods));
cors.setMaxAge(maxAge);
source.registerCorsConfiguration(mapping, cors);
return new CorsWebFilter(source);
}
}
package com.abc.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author abc
* @version since 1.0.0
* @date 2022/8/16 9:03
* @apiNote Cors配置方案
*/
@Order(Ordered.HIGHEST_PRECEDENCE)//HIGHEST_PRECEDENCE:这表示过滤器优先级设置为最高
@Configuration
@Slf4j
public class FateCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
//下面这段是关键,不加的话,返回时Ajax会提示ResponseStatus异常
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
建议在网关模块统一配置:请求都是通过服务网关转发的。
如果只对某一接口配置 CORS
,可以在方法上添加 @CrossOrigin
注解
@CrossOrigin(origins = {"http://localhost:8088", "null"})
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String greetings() {
return "hello!";
}
注:该注解可以在类上添加,表示对该类声明所有接口都有效。
见博客《【漏洞利用】跨域资源共享(CORS)漏洞详解》
阮一峰的网络日志《跨域资源共享 CORS 详解》
维基百科《Cross-origin resource sharing》
前端早读课 《CORS 完全手册之CORS 详解》
跨源资源共享(CORS)
API网关实现跨域资源共享(CORS)