目录
跨域
跨域概念
我们的 Url
的一般格式都是:协议 + 域名(子域名 + 主域名) + 端口号 + 资源地址
- 如
https://www.dustyblog.cn:8080/say/Hello
是由https + www + dustyblog.cn + 8080 + say/Hello
组成
只要 协议
,子域名
,主域名
,端口号
这四项组成部分中 有一项不同
,就可以认为是不同的域,不同的域之间互相访问资源,就被称之为跨域
为何浏览器要遵守同源策略
浏览器遵守同源策略,是为了 安全考虑
,如果不遵守同源策略,那么我的 ajax
可以随便调用任何人的后端隐私接口,我的脚本可以任意读取别人网站下的 cookie
。我把这些恶意脚本放在自己的网站上,只要你访问我的网站,浏览器就会执行我的脚本,我就可以拿到你的所有 cookie
数据
CORS
简介
我现在有 www.abc.com
和 www.xyz.com
,我就想让 abc
下的脚本访问 xyz
下的数据,那么我该怎么办?——利用 CORS
(跨域资源共享)
CORS
允许浏览器向跨源(协议 + 域名 + 端口
)的服务器,发出XMLHttpRequest
请求,从而克服了ajax
只能同源使用的限制CORS
需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与- 对于开发者来说,
CORS
通信与同源的ajax
通信没有差别,代码完全一样 - 浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉
因此,实现 CORS
通信的关键是服务器。只要服务器实现了 CORS
接口,就可以跨源通信
跨域请求时,服务器的响应头是怎样的
跨域情况下,服务器的响应头有以下几种
Access-Control-Allow-Origin
:该字段必填。它的值要么是请求时Origin
字段的具体值,要么是一个*
,表示接受任意域名的请求Access-Control-Allow-Methods
:该字段必填。它的值是逗号分隔的一个具体的字符串或者*
,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求Access-Control-Expose-Headers
:该字段可选。CORS
请求时XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6
个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定Access-Control-Allow-Credentials
:该字段可选。它的值是一个布尔值,表示是否允许发送Cookie
,默认情况下不发送Cookie
。对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
,这个值只能设为true
。如果服务器不要浏览器发送Cookie
,删除该字段即可Access-Control-Max-Age
:该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求
跨域示例及解决
跨域示例
创建两个 springboot
项目,命名为 cross-request-one
和 cross-request-two
cross-request-one
html
页面
创建一个 cross.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Test</h1>
<li><a href="">click</a></li>
</body>
<script type="text/javascript" th:src="@{/jquery/jquery-3.5.1.min.js}"></script>
<script type="text/javascript">
const url = 'http://127.0.0.1:8081/testget';
$(document).ready(function () {
$("a").click(function () {
$.get(url, function (data) {
console.log(data);
});
// 禁止页面跳转
return false;
});
});
</script>
</html>
controller
@Controller
public class CrossController {
@GetMapping(path = "/cross")
public String cross() {
return "cross";
}
}
配置文件
注意端口号是 8080
server.port=8080
spring.mvc.view.prefix=classpath:/templates/
spring.mvc.view.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.cache=false
cross-request-two
配置文件
server.port=8081
controller
@Slf4j
@Controller
public class HelloController {
@GetMapping(path = "/testget")
@ResponseBody
public Map<String, String> testGet() {
Map<String, String> testMap = new HashMap<>();
testMap.put("name", "jack");
log.info("执行了!");
return testMap;
}
}
跨域测试
分别启动两个项目,进行测试
点击超链接后,会发送 ajax
请求到 http://127.0.0.1:8081/testget
,此时由于端口不一致,就产生了跨域请求
跨域解决
springboot 2.x
主要提供了两种方式来支持 CORS
,当然还可以使用 springMVC
拦截器实现跨域
方式 | 作用范围 | 说明 |
---|---|---|
@CrossOrigin 注解 |
一个controller 中全部接口或是其中一个特定的接口 |
配置、定制特定的请求接口 |
WebMvcConfigurer 对象 |
全部接口 | 适用于全局配置 |
使用 @CrossOrigin
注解
@Slf4j
@Controller
public class HelloController {
@CrossOrigin
@GetMapping(path = "/testget")
@ResponseBody
public Map<String, String> testGet() {
Map<String, String> testMap = new HashMap<>();
testMap.put("name", "jack");
log.info("执行了!");
return testMap;
}
}
测试
跨域请求成功
其中,@CrossOrigin
注解可以使用以下参数
名称 | 类型 | 范围 | 必填 | 请求头字段 |
---|---|---|---|---|
value | String数组 | 类或方法 | 是 | Access-Control-Allow-Origin |
origins | String数组 | 类或方法 | 是,同value,可以二选一 | Access-Control-Allow-Origin |
methods | String数组 | 类或接口 | 是 | Access-Control-Allow-Methods |
maxAge | long | 类或接口 | 否 | Access-Control-Max-Age |
allowCredentials | String | 类或接口 | 否 | Access-Control-Allow-Credentials |
allowedHeaders | String数组 | 类或接口 | 否 | Access-Control-Request-Headers |
exposedHeaders | String数组 | 类或接口 | 否 | Access-Control-Expose-Headers |
value、origins
属性:配置允许访问的源,如http://anxminise.cc
,*
表示允许全部的域名methods
属性:配置跨域请求支持的方式,如GET、POST
,且一次性返回全部支持的方式maxAge
属性:配置预检请求的有效时间, 单位是秒,表示:在多长时间内,不需要发出第二次预检请求allowCredentials
属性:配置是否允许发送Cookie
,用于凭证请求, 默认不发送cookie
allowedHeaders
属性:配置允许的自定义请求头,用于预检请求exposedHeaders
属性:配置响应的头信息, 在其中可以设置其他的头信息,不进行配置时,默认可以获取到Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
字段
WebMvcConfigurer
项目中添加一个配置类即可
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(@NotNull CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
测试
注释掉注解 @CrossOrigin
,跨域请求成功
其中,通过相应的方法实现跨域请求的配置
方法类 | 方法名称 | 必填 | 请求头字段 | 说明 |
---|---|---|---|---|
CorsRegistry | addMapping | 是 | 无, 非Cors属性, 属于SpringBoot配置 |
配置支持跨域的路径 |
CorsRegistration | allowedOrigins | 是 | Access-Control-Allow-Origin | 配置允许的源 |
CorsRegistration | allowedMethods | 是 | Access-Control-Allow-Methods | 配置支持跨域请求的方法, 如:GET、POST,一次性返回 |
CorsRegistration | maxAge | 否 | Access-Control-Max-Age | 配置预检请求的有效时间 |
CorsRegistration | allowCredentials | 否 | Access-Control-Allow-Credentials | 配置是否允许发送Cookie, 用于 凭证请求 |
CorsRegistration | allowedHeaders | 否 | Access-Control-Request-Headers | 配置允许的自定义请求头, 用于 预检请求 |
CorsRegistration | exposedHeaders | 否 | Access-Control-Expose-Headers | 配置响应的头信息, 在其中可以设置其他的头信息 |
使用拦截器
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "86400");
response.setHeader("Access-Control-Allow-Headers", "*");
// 如果是OPTIONS则直接返回无响应体的响应
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
并在配置中应用该拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 不拦截静态资源
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
// 这样在项目启动是就会默认访问resource下的static文件夹的index.html
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有请求,允许跨域
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/**");
}
}