Spring MVC 对CORS的支持
2017-01-19 10:34
501 查看
当一个资源请求一个其它域名的资源时会发起一个跨域HTTP请求(cross-origin HTTP request)。比如说,域名A(http://domaina.example)的某 Web 应用通过<img>标签引入了域名B(http://domainb.foo)的某图片资源(http://domainb.foo/image.jpg),域名A的 Web 应用就会导致浏览器发起一个跨域 HTTP 请求。在当今的 Web 开发中,使用跨域 HTTP 请求加载各类资源(包括CSS、图片、JavaScript
脚本以及其它类资源),已经成为了一种普遍且流行的方式。
正如大家所知,出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用
HTTP 请求就必须遵守同源策略。 具体而言,Web 应用程序通过
对象或Fetch能且只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。为了能开发出更强大、更丰富、更安全的Web应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。比如,可以使用
发起跨站 HTTP 请求。(这段描述跨域不准确,跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是CSRF跨站攻击原理,请求是发送到了后端服务器无论是否跨域!注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)
隶属于
W3C 的 Web 应用工作组推荐了一种新的机制,即跨源资源共享 (CORS)。这种机制让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。需要特别注意的是,这个规范是针对API容器的(比如说
或者
Fetch ),以减轻跨域HTTP请求的风险。
CORS 将请求分为两类:简单请求和非简单请求。
简单请求对应该规则,因此对简单请求的定义为:
请求方法是
比如有一个简单请求:
GET /test HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.examples.com Host: www.examples.com
对于这样的简单请求, CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。
如果允许,则在 HTTP 头信息中添加
如果不允许,则不在头信息中添加
浏览器先于用户得到返回结果,根据有无
对于 CORS 出现前的一些服务, CORS 对他们的影响分两种情况:
script 或者 image 触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。
如果是 ajax 请求, HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含
可以看出, CORS 的出现,没有对”旧的“服务造成任何影响。
另外,除了提到的
Access-Control-Allow-Credentials: 可选,用户是否可以发送、处理 cookie 。
Access-Control-Expose-Headers :可选,可以让用户拿到的字段。有几个字段无论设置与否都可以拿到的,包括: Cache-Control 、 Content-Language 、 Content-Type 、 Expires 、 Last-Modified 、 Pragma 。
对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次 OPTION 请求,称为预检请求( preflight request )。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。
比如对于
与 CORS 相关的字段有:
服务器收到请求时,需要分别对 Origin 、 Access-Control-Request-Method 、 Access-Control-Request-Headers 进行验证,验证通过后,会在返回 Http 头信息中添加
Access-Control-Allow-Methods: 真实请求允许的方法
Access-Control-Allow-Headers: 服务器允许使用的字段
Access-Control-Allow-Credentials: 是否允许用户发送、处理 cookie
Access-Control-Max-Age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求
当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求。
了解完 CORS ,接下来我们来搭建简单的 Spring MVC 服务,并进一步了解 Spring MVC 如何配置 CORS
简单跨域就是GET,HEAD和POST请求,但是POST请求的"Content-Type"只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain
反之,就是非简单跨域,此跨域有一个预检机制,说直白点,就是会发两次请求,一次OPTIONS请求,一次真正的请求
首先新建一个静态web项目,定义三种类型的请求:简单跨域请求,非简单跨域请求,带Cookie信息的请求(做登录校验)。代码如下:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>跨域demo</title>
<link rel="stylesheet" href="node_modules/amazeui/dist/css/amazeui.min.css">
</head>
<body class="am-container">
<!--简单跨域-->
<button class="am-btn am-btn-primary" onclick="getUsers(this)">
简单跨域: 获取用户列表
</button>
<p class="am-text-danger"></p>
<!--非简单跨域-->
<button class="am-btn am-btn-primary" onclick="addUser(this)">
非简单跨域: 添加用户(JSON请求)
</button>
<input type="text" placeholder="用户名">
<p class="am-text-danger"></p>
<!--检查是否登录-->
<button class="am-btn am-btn-primary am-margin-right" onclick="checkLogin(this)">
登录校验
</button>
<p class="am-text-danger"></p>
<!--登录-->
<button class="am-btn am-btn-primary" onclick="login(this)">
登录
</button>
<input type="text" placeholder="用户名">
<p class="am-text-danger"></p>
</body>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/amazeui/dist/js/amazeui.js"></script>
<script>
function getUsers(btn) {
var $btn = $(btn);
$.ajax({
type: 'get',
url: 'http://localhost:8080/api/users',
contentType: "application/json;charset=UTF-8"
}).then(
function (obj) {
$btn.next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('p').html('error...');
}
)
}
function addUser(btn) {
var $btn = $(btn);
var name = $btn.next('input').val();
if (!name) {
$btn.next('input').next('p').html('用户名不能为空');
return;
}
$.ajax({
type: 'post',
url: 'http://localhost:8080/api/users',
contentType: "application/json;charset=UTF-8",
data: name,
dataType: 'json'
}).then(
function (obj) {
$btn.next('input').next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('input').next('p').html('error...');
}
)
}
function checkLogin(btn) {
var $btn = $(btn);
$.ajax({
type: 'get',
url: 'http://localhost:8080/api/user/login',
contentType: "application/json;charset=UTF-8",
xhrFields: {
withCredentials: true
}
}).then(
function (obj) {
$btn.next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('p').html('error...');
}
)
}
function login(btn) {
var $btn = $(btn);
var name = $btn.next('input').val();
if (!name) {
$btn.next('input').next('p').html('用户名不能为空');
return;
}
$.ajax({
type: 'post',
url: 'http://localhost:8080/api/user/login',
contentType: "application/json;charset=UTF-8",
data: name,
dataType: 'json',
xhrFields: {
withCredentials: true
}
}).then(
function (obj) {
$btn.next('input').next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('input').next('p').html('error...');
}
)
}
</script>
</html>
然后启动web项目(这里推荐一个所见即所得工具:browser-sync)
接来下,做服务端的事情,新建一个SpringMVC项目,这里推荐一个自动生成Spring种子项目的网站:http://start.spring.io/
种子项目
项目结构如下:
项目结构
在pom.xml中引入lombok和guava<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
模拟数据源:UserDB
编写示例控制器:UserController
编写示例控制器:UserLoginController
最后启动服务端项目
到这里,主要工作都完成了,打开浏览器,访问静态web项目,打开控制台,发现Ajax请求无法获取数据,这就是同源策略的限制
下面我们一步步来开启服务端的CORS支持
CorsFilter: 过滤器阶段的CORS
现在测试一下“简单跨域”和“非简单跨域”,已经可以正常响应了
浏览器图片
再来测试一下 “登录校验” 和 “登录”,看看cookie是否能正常跨域
浏览器图片
如果把服务端的allowCredentials设为false,或者ajax请求中不带{withCredentials: true},那么登录校验永远都是未登录,因为cookie没有在浏览器和服务器之间传递
关于这个阶段的CORS,不得不吐槽几句,Spring把CorsInteceptor写死在了拦截器链上的最后一个,也就是说如果我有自定义的Interceptor,请求一旦被我自己的拦截器拦截下来,则只能通过CorsFilter授权跨域,压根走不到CorsInterceptor,至于为什么,下面会讲到。
所以说CorsInterceptor是专为授权Handler中的跨域而写的。
废话不多说,直接上代码:@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public FilterRegistrationBean corsFilterRegistrationBean() {
// 对响应头进行CORS授权
MyCorsRegistration corsRegistration = new MyCorsRegistration("/**");
this._configCorsParams(corsRegistration);
// 注册CORS过滤器
UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
configurationSource.registerCorsConfiguration("/**", corsRegistration.getCorsConfiguration());
CorsFilter corsFilter = new CorsFilter(configurationSource);
return new FilterRegistrationBean(corsFilter);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// 配置CorsInterceptor的CORS参数
this._configCorsParams(registry.addMapping("/**"));
}
private void _configCorsParams(CorsRegistration corsRegistration) {
corsRegistration.allowedOrigins(CrossOrigin.DEFAULT_ORIGINS)
.allowedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name(), HttpMethod.PUT.name())
.allowedHeaders(CrossOrigin.DEFAULT_ALLOWED_HEADERS)
.exposedHeaders(HttpHeaders.SET_COOKIE)
.allowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS)
.maxAge(CrossOrigin.DEFAULT_MAX_AGE);
}
}
打开浏览器,效果和上面一样
这是最小控制粒度了,可以精确到某个请求的跨域控制// 先把WebConfig中前两阶段的配置注释掉,再到这里加跨域注解
@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping(method = RequestMethod.GET)
List<User> getList() {
return Lists.newArrayList(userdb.asMap().values());
}
打开浏览器,发现只有第一个请求可以正常跨域
官网:http://software.dzhuvinov.com/cors-filter.html
喜欢用这个CORSFilter主要是因为它支持CORS配置文件,能够自动读取classpath下的cors.properties,还有file watching的功能
脚本以及其它类资源),已经成为了一种普遍且流行的方式。
正如大家所知,出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用
XMLHttpRequest对象和Fetch发起
HTTP 请求就必须遵守同源策略。 具体而言,Web 应用程序通过
XMLHttpRequest
对象或Fetch能且只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。为了能开发出更强大、更丰富、更安全的Web应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。比如,可以使用
XMLHttpRequest
发起跨站 HTTP 请求。(这段描述跨域不准确,跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是CSRF跨站攻击原理,请求是发送到了后端服务器无论是否跨域!注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)
隶属于
W3C 的 Web 应用工作组推荐了一种新的机制,即跨源资源共享 (CORS)。这种机制让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。需要特别注意的是,这个规范是针对API容器的(比如说
XMLHttpRequest
或者
Fetch ),以减轻跨域HTTP请求的风险。
CORS 将请求分为两类:简单请求和非简单请求。
1.1 简单请求
可以先看下 CORS 出现前的情况:跨源时能够通过 script 或者 image 标签触发 GET 请求或通过表单发送一条 POST 请求,但这两种请求 HTTP 头信息中都不能包含任何自定义字段。简单请求对应该规则,因此对简单请求的定义为:
请求方法是
HEAD、
GET或
POST且 HTTP 头信息不超过以下几个字段:
Accept、
Accept-Language、
Content-Language、
Last-Event-ID、
Content-Type(只限于
application/x-www-form-urlencoded、
multipart/form-data、
text/plain)。
比如有一个简单请求:
GET /test HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.examples.com Host: www.examples.com
对于这样的简单请求, CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。
如果允许,则在 HTTP 头信息中添加
Access-Control-Allow-Origin字段,并返回正确的结果
如果不允许,则不在头信息中添加
Access-Control-Allow-Origin字段。
浏览器先于用户得到返回结果,根据有无
Access-Control-Allow-Origin字段来决定是否拦截该返回结果。
对于 CORS 出现前的一些服务, CORS 对他们的影响分两种情况:
script 或者 image 触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。
如果是 ajax 请求, HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含
Access-Control-Allow-Origin,因此返回结果会被浏览器拦截,接口依旧不可以被 ajax 跨源访问。
可以看出, CORS 的出现,没有对”旧的“服务造成任何影响。
另外,除了提到的
Access-Control-Allow-Origin还有几个字段用于描述 CORS 返回结果:
Access-Control-Allow-Credentials: 可选,用户是否可以发送、处理 cookie 。
Access-Control-Expose-Headers :可选,可以让用户拿到的字段。有几个字段无论设置与否都可以拿到的,包括: Cache-Control 、 Content-Language 、 Content-Type 、 Expires 、 Last-Modified 、 Pragma 。
1.2 非简单请求
在真正请求之前发送一次 OPTION 请求的,就是非简单请求。对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次 OPTION 请求,称为预检请求( preflight request )。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。
比如对于
DELETE请求:
OPTIONS /test HTTP/1.1 Origin: http://www.examples.com Access-Control-Request-Method: DELETE Access-Control-Request-Headers: X-Custom-Header Host: www.examples.com
与 CORS 相关的字段有:
Access-Control-Request-Method: 真实请求使用的 HTTP 方法。
Access-Control-Request-Headers: 真实请求中包含的自定义头字段。
服务器收到请求时,需要分别对 Origin 、 Access-Control-Request-Method 、 Access-Control-Request-Headers 进行验证,验证通过后,会在返回 Http 头信息中添加
Access-Control-Allow-Origin: http://www.examples.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000
他们的含义分别是:
Access-Control-Allow-Methods: 真实请求允许的方法
Access-Control-Allow-Headers: 服务器允许使用的字段
Access-Control-Allow-Credentials: 是否允许用户发送、处理 cookie
Access-Control-Max-Age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求
当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求。
了解完 CORS ,接下来我们来搭建简单的 Spring MVC 服务,并进一步了解 Spring MVC 如何配置 CORS
初始项目准备
补充一下,对于简单跨域和非简单跨域,可以这么理解:简单跨域就是GET,HEAD和POST请求,但是POST请求的"Content-Type"只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain
反之,就是非简单跨域,此跨域有一个预检机制,说直白点,就是会发两次请求,一次OPTIONS请求,一次真正的请求
首先新建一个静态web项目,定义三种类型的请求:简单跨域请求,非简单跨域请求,带Cookie信息的请求(做登录校验)。代码如下:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>跨域demo</title>
<link rel="stylesheet" href="node_modules/amazeui/dist/css/amazeui.min.css">
</head>
<body class="am-container">
<!--简单跨域-->
<button class="am-btn am-btn-primary" onclick="getUsers(this)">
简单跨域: 获取用户列表
</button>
<p class="am-text-danger"></p>
<!--非简单跨域-->
<button class="am-btn am-btn-primary" onclick="addUser(this)">
非简单跨域: 添加用户(JSON请求)
</button>
<input type="text" placeholder="用户名">
<p class="am-text-danger"></p>
<!--检查是否登录-->
<button class="am-btn am-btn-primary am-margin-right" onclick="checkLogin(this)">
登录校验
</button>
<p class="am-text-danger"></p>
<!--登录-->
<button class="am-btn am-btn-primary" onclick="login(this)">
登录
</button>
<input type="text" placeholder="用户名">
<p class="am-text-danger"></p>
</body>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/amazeui/dist/js/amazeui.js"></script>
<script>
function getUsers(btn) {
var $btn = $(btn);
$.ajax({
type: 'get',
url: 'http://localhost:8080/api/users',
contentType: "application/json;charset=UTF-8"
}).then(
function (obj) {
$btn.next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('p').html('error...');
}
)
}
function addUser(btn) {
var $btn = $(btn);
var name = $btn.next('input').val();
if (!name) {
$btn.next('input').next('p').html('用户名不能为空');
return;
}
$.ajax({
type: 'post',
url: 'http://localhost:8080/api/users',
contentType: "application/json;charset=UTF-8",
data: name,
dataType: 'json'
}).then(
function (obj) {
$btn.next('input').next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('input').next('p').html('error...');
}
)
}
function checkLogin(btn) {
var $btn = $(btn);
$.ajax({
type: 'get',
url: 'http://localhost:8080/api/user/login',
contentType: "application/json;charset=UTF-8",
xhrFields: {
withCredentials: true
}
}).then(
function (obj) {
$btn.next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('p').html('error...');
}
)
}
function login(btn) {
var $btn = $(btn);
var name = $btn.next('input').val();
if (!name) {
$btn.next('input').next('p').html('用户名不能为空');
return;
}
$.ajax({
type: 'post',
url: 'http://localhost:8080/api/user/login',
contentType: "application/json;charset=UTF-8",
data: name,
dataType: 'json',
xhrFields: {
withCredentials: true
}
}).then(
function (obj) {
$btn.next('input').next('p').html(JSON.stringify(obj));
},
function () {
$btn.next('input').next('p').html('error...');
}
)
}
</script>
</html>
然后启动web项目(这里推荐一个所见即所得工具:browser-sync)
browser-sync start --server --files "*.html"
接来下,做服务端的事情,新建一个SpringMVC项目,这里推荐一个自动生成Spring种子项目的网站:http://start.spring.io/
种子项目
项目结构如下:
项目结构
在pom.xml中引入lombok和guava<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
模拟数据源:UserDB
public class UserDB { public static Cache<String, User> userdb = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.DAYS).build(); static { String id1 = UUID.randomUUID().toString(); String id2 = UUID.randomUUID().toString(); String id3 = UUID.randomUUID().toString(); userdb.put(id1, new User(id1, "jear")); userdb.put(id2, new User(id2, "tom")); userdb.put(id3, new User(id3, "jack")); } }
编写示例控制器:UserController
@RestController @RequestMapping("/users") public class UserController { @RequestMapping(method = RequestMethod.GET) List<User> getList() { return Lists.newArrayList(userdb.asMap().values()); } @RequestMapping(method = RequestMethod.POST) List<String> add(@RequestBody String name) { if (userdb.asMap().values().stream().anyMatch(user -> user.getName().equals(name))) { return Lists.newArrayList("添加失败, 用户名'" + name + "'已存在"); } String id = UUID.randomUUID().toString(); userdb.put(id, new User(id, name)); return Lists.newArrayList("添加成功: " + userdb.getIfPresent(id)); } }
编写示例控制器:UserLoginController
@RestController @RequestMapping("/user/login") public class UserLoginController { @RequestMapping(method = RequestMethod.GET) Object getInfo(HttpSession session) { Object object = session.getAttribute("loginer"); return object == null ? Lists.newArrayList("未登录") : object; } @RequestMapping(method = RequestMethod.POST) List<String> login(HttpSession session, @RequestBody String name) { Optional<User> user = userdb.asMap().values().stream().filter(user1 -> user1.getName().equals(name)).findAny(); if (user.isPresent()) { session.setAttribute("loginer", user.get()); return Lists.newArrayList("登录成功!"); } return Lists.newArrayList("登录失败, 找不到用户名:" + name); } }
最后启动服务端项目
mvn clean package debug模式启动Application
到这里,主要工作都完成了,打开浏览器,访问静态web项目,打开控制台,发现Ajax请求无法获取数据,这就是同源策略的限制
下面我们一步步来开启服务端的CORS支持
重点:三个阶段的CROS处理方式
CorsFilter: 过滤器阶段的CORS
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Bean public FilterRegistrationBean filterRegistrationBean() { // 对响应头进行CORS授权 MyCorsRegistration corsRegistration = new MyCorsRegistration("/**"); corsRegistration.allowedOrigins(CrossOrigin.DEFAULT_ORIGINS) .allowedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name(), HttpMethod.PUT.name()) .allowedHeaders(CrossOrigin.DEFAULT_ALLOWED_HEADERS) .exposedHeaders(HttpHeaders.SET_COOKIE) .allowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS) .maxAge(CrossOrigin.DEFAULT_MAX_AGE); // 注册CORS过滤器 UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource(); configurationSource.registerCorsConfiguration("/**", corsRegistration.getCorsConfiguration()); CorsFilter corsFilter = new CorsFilter(configurationSource); return new FilterRegistrationBean(corsFilter); } }
现在测试一下“简单跨域”和“非简单跨域”,已经可以正常响应了
浏览器图片
再来测试一下 “登录校验” 和 “登录”,看看cookie是否能正常跨域
浏览器图片
如果把服务端的allowCredentials设为false,或者ajax请求中不带{withCredentials: true},那么登录校验永远都是未登录,因为cookie没有在浏览器和服务器之间传递
CorsInterceptor: 拦截器阶段的CORS
既然已经有了Filter级别的CORS,为什么还要CorsInterceptor呢?因为控制粒度不一样!Filter是任意Servlet的前置过滤器,而Inteceptor只对DispatcherServlet下的请求拦截有效,它是请求进入Handler的最后一道防线,如果再设置一层Inteceptor防线,可以增强安全性和可控性。关于这个阶段的CORS,不得不吐槽几句,Spring把CorsInteceptor写死在了拦截器链上的最后一个,也就是说如果我有自定义的Interceptor,请求一旦被我自己的拦截器拦截下来,则只能通过CorsFilter授权跨域,压根走不到CorsInterceptor,至于为什么,下面会讲到。
所以说CorsInterceptor是专为授权Handler中的跨域而写的。
废话不多说,直接上代码:@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public FilterRegistrationBean corsFilterRegistrationBean() {
// 对响应头进行CORS授权
MyCorsRegistration corsRegistration = new MyCorsRegistration("/**");
this._configCorsParams(corsRegistration);
// 注册CORS过滤器
UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
configurationSource.registerCorsConfiguration("/**", corsRegistration.getCorsConfiguration());
CorsFilter corsFilter = new CorsFilter(configurationSource);
return new FilterRegistrationBean(corsFilter);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// 配置CorsInterceptor的CORS参数
this._configCorsParams(registry.addMapping("/**"));
}
private void _configCorsParams(CorsRegistration corsRegistration) {
corsRegistration.allowedOrigins(CrossOrigin.DEFAULT_ORIGINS)
.allowedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name(), HttpMethod.PUT.name())
.allowedHeaders(CrossOrigin.DEFAULT_ALLOWED_HEADERS)
.exposedHeaders(HttpHeaders.SET_COOKIE)
.allowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS)
.maxAge(CrossOrigin.DEFAULT_MAX_AGE);
}
}
打开浏览器,效果和上面一样
@CrossOrigin:Handler阶段的CORS
如果把前面的代码认真写一遍,应该已经发现这个注解了,这个注解是用在控制器方法上的,其实Spring在这里用的还是CorsInterceptor,做最后一层拦截,这也就解释了为什么CorsInterceptor永远是最后一个执行的拦截器。这是最小控制粒度了,可以精确到某个请求的跨域控制// 先把WebConfig中前两阶段的配置注释掉,再到这里加跨域注解
@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping(method = RequestMethod.GET)
List<User> getList() {
return Lists.newArrayList(userdb.asMap().values());
}
打开浏览器,发现只有第一个请求可以正常跨域
追求更好的开发体验:整合第三方CORSFilter
对这个类库的使用和分析将在下一篇展开官网:http://software.dzhuvinov.com/cors-filter.html
喜欢用这个CORSFilter主要是因为它支持CORS配置文件,能够自动读取classpath下的cors.properties,还有file watching的功能
相关文章推荐
- Spring MVC 4.2 增加 CORS 支持 Cross-Origin
- Spring MVC 4.2 增加 CORS 支持 (跨域请求)
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC 4.2 增加 CORS 支持
- Spring MVC Rest 支持CORS
- Spring MVC 4.2 增加 CORS 支持
- ie 的XDomainRequest对于cors支持的资料
- 在dotnet core web api中支持CORS(跨域访问)
- spring mvc对Json的支持
- SpringMVC支持跨域访问的CORS配置
- Spring MVC 支持 RESTful 风格编程
- spring mvc支持返回json
- Spring MVC 3.2 技术预览(一):Servlet 3介绍,异步支持