前言
跨域问题是浏览器为了保护用户的信息安全,实施了同源策略(Same-Origin Policy),即只允许页面请求同源(相同协议、域名和端口)的资源,当 JavaScript 发起的请求跨越了同源策略,即请求的目标与当前页面的域名、端口、协议不一致时,浏览器会阻止请求的发送或接收。
同源策略/SOP(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,现在所有支持 JavaScript 的浏览器都会使用这个策略。如果缺少了同源策略,浏览器很容易受到 XSS、 CSFR 等攻击。
同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。
跨域主要涉及4个响应头
-
Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)
-
Access-Control-Allow-Headers 跨域允许携带的特殊头信息字段 (只在预检请求验证)
-
Access-Control-Allow-Methods 跨域允许的请求方法或者说HTTP动词 (只在预检请求验证)
-
Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。不过不建议跨域使用(项目中用到过,不过不稳定,有些浏览器带不过去),除非必要,因为有很多方案可以代替。
1)SpringBoot解决跨域
1.使用 @CrossOrigin 注解实现跨域【局域类跨域】
此注解既可以修饰类,也可以修饰方法。当修饰类时,表示此类中的所有接口都可以跨域;当修饰方法时,表示此方法可以跨域
@CrossOrigin(origins = "*") //origins可以指定请求来源,*代表全部
2.通过配置文件实现跨域【全局跨域】
#yml配置
open:
allow:
origin: '*'
import .slf4j.Slf4j;
import .;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Slf4j
@Configuration
public class CorsConfig {
@Value("${:*}")
private String allowOrigin;
@Bean
public CorsFilter corsFilter() {
//
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1,允许任何来源
corsConfiguration
.setAllowedOriginPatterns(((allowOrigin, ",")));
//2,允许任何请求头
();
//3,允许任何方法
();
//4,允许凭证
(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
("/**", corsConfiguration);
return new CorsFilter(source);
}
}
#yml配置
open:
allow:
origin: ,
@Slf4j
@Configuration
@Profile("prod")
public class CorsConfig {
@Value("${}")
private String allowOrigin;
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
for (String r : (",")) {
(r);
}
("*");
();
();
(true);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
("/**", buildConfig());
return new CorsFilter(source);
}
}
3.通过 CorsFilter 对象实现跨域【全局跨域】
import ;
import ;
import ;
import ;
import ;
@Configuration
public class MyCorsFilter {
@Bean
public CorsFilter corsFilter() {
// 创建 CORS 配置对象
CorsConfiguration config = new CorsConfiguration();
// 支持域
("*");
// 是否发送 Cookie
(true);
// 支持请求方式
("*");
// 允许的原始请求头部信息
("*");
// 暴露的头部信息
("*");
// 添加地址映射
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
("/**", config);
// 返回 CorsFilter 对象
return new CorsFilter(corsConfigurationSource);
}
}
4.通过 Response 对象实现跨域【局域方法跨域】
import ;
import ;
import ;
import ;
@RestController
public class TestController {
@RequestMapping("/test")
public Result<Object> test(HttpServletResponse response) {
("Access-Control-Allow-Origin", "*");
return (new HashMap<String, Object>() {{
put("state", 200);
put("data", "跨域访问");
put("msg", ""));
}};
}
}
5.通过实现 ResponseBodyAdvice 实现跨域【全局跨域】
import ;
import ;
import ;
import ;
import ;
import ;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
().set("Access-Control-Allow-Origin", "*");
return body;
}
}
2)Nginx解决跨越
server {
listen 443 ssl;
server_name ;
ssl_certificate /usr/local/nginx/conf/cert/;
ssl_certificate_key /usr/local/nginx/conf/cert/;
location /api/ {
# 允许跨域请求的域名,* 表示允许所有域名访问
add_header 'Access-Control-Allow-Origin' '*';
# 允许跨域请求的方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# 允许跨域请求的自定义 Header
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
# 允许跨域请求的 Credential,如果需要传递Cookie就需要开启此项
add_header 'Access-Control-Allow-Credentials' 'true';
# 预检请求的存活时间,即 Options 请求的响应缓存时间
add_header 'Access-Control-Max-Age' 3600;
# 处理预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://127.0.0.1:8080/;
}
server {
listen 80;
server_name ;
#add_header Access-Control-Allow-Origin '*';
#add_header Access-Control-Allow-Methods '*';
#add_header Access-Control-Allow-Headers '*';
#add_header Access-Control-Allow-Credentials 'true';
rewrite ^(.*)$ https://${server_name}$1 permanent;
}
}
注意:nginx与springboot跨域配置留一个即可
3)网关解决跨域
Spring Cloud Gateway 中解决跨域问题可以通过以下方式实现
1.gateway设置允许跨域
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"
default-filters:
- DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
2.手动写一个 CorsResponseHeaderFilter 的 GlobalFilter 去修改Response中的头
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
private static final String ANY = "*";
@Override
public int getOrder() {
// 指定此过滤器位于NettyWriteResponseFilter之后
// 即待处理完响应体后接着处理响应头
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
@SuppressWarnings("serial")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return (exchange).then((() -> {
().getHeaders().entrySet().stream()
.filter(kv -> (() != null && ().size() > 1))
.filter(kv -> (().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| ().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
|| ().equals()))
.forEach(kv ->
{
// Vary只需要去重即可
if(().equals())
(().stream().distinct().collect(()));
else{
List<String> value = new ArrayList<>();
if(().contains(ANY)){ //如果包含*,则取*
(ANY);
(value);
}else{
(().get(0)); // 否则默认取第一个
(value);
}
}
});
}));
}
}
4) 使用代理
在Vue的开发环境中,可以在文件中配置代理,将API请求代理到后端服务器上,从而避免跨域问题。
= {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080', // 后端服务实际地址
changeOrigin: true, // 是否改变域名
pathRewrite: {
'^/api': '' // 路径重写,将前缀/api转为/
}
}
}
}
}
5)JSONP
JSONP(JSON with Padding)是另一种解决跨域的方法,但它只支持GET请求。Spring Boot可以配置JSONP的响应,而前端需要改写发送请求的方式。
6)中间件
可以在中创建一个中间件来转发请求,这样前端请求首先发送到服务器,服务器再将请求转发到后端API,以此绕过浏览器的同源策略限制。
注意事项
- 在开发环境中解决跨域问题时,要注意不要将敏感信息(如Token)暴露给不安全的跨域请求。
- 在生产环境中,推荐使用Nginx反向代理或其他服务器端解决方案,以提高安全性。