前话
对于前后端分离的项目来说,如果前端项目与后端项目部署在两个不同的域下,那么势必会引起跨域问题的出现。
方法1 JSONP
第一个解决方案就是jsonp,并且以前处理跨域问题基本也是这么处理。
在前端写一个类似于Ajax的异步请求,解析回调函数返回的结果参数,Ajax直接请求普通文件存在跨域无权限访问的问题,不管是静态页面、动态网页、web服务、WCF,只要是跨域请求,一律不准,但是凡是拥有”src” 这个属性的标签都拥有跨域的能力,如<\script>、<\img>、<\iframe>。
( ajax和jsonp这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装。但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加。
出于安全考虑,浏览器会限制脚本中发起的跨站请求。使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy))*
如下示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 实例</title>
<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="divCustomers"></div>
<script>
$.getJSON("https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=?", function(data) {
var html = ''
;
for(var i = 0; i < data.length; i++)
{
html += '' + data[i] + '';
}
html += '';
$('#divCustomers').html(html);
});
</script>
</body>
</html>
但是jsonp方式也同样有不足,不管是对于前端还是后端来说,写法与我们平常的ajax写法不同,同样后端也需要作出相应的更改。并且,jsonp方式只能通过get请求方式来传递参数,当然也还有其它的不足之处,这里就不一一赘述了。
方法2 通过cors协议解决跨域问题
Spring MVC 从4.2版本开始增加了对CORS的支持。Cross-Origin Resource Sharing(跨域资源共享)。通过它,我们可以决定资源是否能被跨域访问。cors是一个w3c标准,它允许浏览器(目前ie8以下还不能被支持)向我们不同源的服务器发出XmlHttpRequest请求,我们可以继续使用ajax进行请求访问。
在SpringBoot中提供Cros协议配置的方式有两种:
1.通过过滤器实现
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
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 req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (StringUtils.isNotBlank(allowOrigin)) {
List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
if (CollectionUtils.isNotEmpty(allowOriginList)) {
String currentOrigin = request.getHeader("Origin");
if (allowOriginList.contains(currentOrigin)) {
response.setHeader("Access-Control-Allow-Origin", currentOrigin);
}
}
}
if (StringUtils.isNotBlank(allowMethods)) {
response.setHeader("Access-Control-Allow-Methods", allowMethods);
}
if (StringUtils.isNotBlank(allowCredentials)) {
response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
}
if (StringUtils.isNotBlank(allowHeaders)) {
response.setHeader("Access-Control-Allow-Headers", allowHeaders);
}
if (StringUtils.isNotBlank(exposeHeaders)) {
response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
}
filterChain.doFilter(req, res);
}
@Override
public void destroy() {
}
以上 CorsFilter 将从 web.xml 中读取相关 Filter 初始化参数,并将在处理 HTTP 请求时将这些参数写入对应的 CORS 响应头中,下面大致描述一下这些 CORS 响应头的意义:
需要注意的是,CORS 规范中定义 Access-Control-Allow-Origin 只允许两种取值,要么为 *,要么为具体的域名,也就是说,不支持同时配置多个域名。为了解决跨多个域的问题,需要在代码中做一些处理,这里将 Filter 初始化参数作为一个域名的集合(用逗号分隔),只需从当前请求中获取 Origin 请求头,就知道是从哪个域中发出的请求,若该请求在以上允许的域名集合中,则将其放入 Access-Control-Allow-Origin 响应头,这样跨多个域的问题就轻松解决了。
可以在web.xml 中配置过滤器来生效,如下配置
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.xxx.api.cors.CorsFilter</filter-class>
<init-param>
<param-name>allowOrigin</param-name>
<param-value>http://web.xxx.com</param-value>
</init-param>
<init-param>
<param-name>allowMethods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<param-name>allowCredentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>allowHeaders</param-name>
<param-value>Content-Type</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在springboot中可以配置生效,如下
import com.fpd.order.filter.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
@Configuration
public class FilterBeanConfig {
@Bean
public FilterRegistrationBean corsFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CorsFilter());
ArrayList<String> list = new ArrayList<>();
list.add("*");
registrationBean.setUrlPatterns(list);
return registrationBean;
}
}
在Controller上使用@CrossOrigin注解
@CrossOrigin(origins = "http://xxx.com", maxAge = 3600)
@RestController
@RequestMapping("/")
public class AccountController {
...
}
在方法上使用@CrossOrigin注解
@CrossOrigin("http://xxx.com")
@RequestMapping("/")
public Result get(@PathVariable Long id) {
// ...
}