文章目录
跨域问题
概述
在 HTML 中,<a>
, <form>
, <img>
, <script>
, <iframe>
, <link>
等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域。
举个栗子,比如当前网址是www.test.com
,而我想访问images.test.com
,那么这就算是跨域请求了(域名不同);又或者在www.test.com:8080
这个网址去访问www.test.com:8081
,那么这也是跨域请求(端口号不同);又比如在http://www.test.com
去访问https://www.test.com
,那么这个也是跨域请求(协议不同)。
跨域缘由
主要源自浏览器制定的同源策略(Same origin policy),何为同源策略?所谓同源就是指上面提到的"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
跨域的后果
由于同源策略造成的跨域有什么结果呢?一般会有下面几种限制:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 Js对象无法获得
- AJAX 请求不能发送(实际是请求能发送过去,但是结果拿不到,会被拦截下来)
问题解决方案
JSONP
前端页面示例
原生JS实现
<body>
<input type="text" id="msg"> <br />
<input type="button" value="加载" onclick="loadData()" />
</body>
<script type="text/javascript">
function setVal(data) {
console.log(data);
$("#msg").val(data);
}
function loadData() {
var script = $("<script>");
script.attr("src", "http://localhost:8081/loadData?callback=setVal");
script.attr("type", "text/javascript");
$("head").append(script);
}
</script>
jQuery实现
<body>
<input type="text" id="msg"> <br />
<input type="button" value="加载" onclick="loadData()" />
</body>
<script type="text/javascript">
function setVal(data) {
console.log(data);
$("#msg").val(data);
}
function loadData() {
$.ajax({
url: 'http://localhost:8080/loadData',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "setVal", // 自定义回调函数名
data: {}
});
}
</script>
后端示例
原始servlet的Java代码
/**
* 通过处理JSONP请求,解决跨域问题
*/
@WebServlet("/loadData")
public class TestServlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = "Hello JSONP";
String callbackFn = req.getParameter("callback");
msg = callbackFn + "('" + msg + "');";
resp.getWriter().println(msg);
}
}
基于Spring MVC的Java代码
@RequestMapping("/loadData")
@ResponseBody
public Object loadData(String callback) {
String msg = "Hello JSONP";
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(msg);
mappingJacksonValue.setJsonpFunction(callback);
return mappingJacksonValue;
}
注: 仅Spring4.x系列支持,5.1以后删除了
通过@CrossOrigin注解实现
这种方式是纯粹的Java后台实现的跨域,前端页面可以照常使用Ajax请求
前端页面
照常发送Ajax请求就行
<body>
<input type="text" id="msg"> <br />
<input type="button" value="加载" onclick="loadData()" />
</body>
<script type="text/javascript">
function loadData() {
var url = "http://localhost:8080/loadData";
$.get(url, function (data) {
console.log(data);
$("#msg").val(data);
});
}
</script>
后台代码
这种方式就只能依靠spring的强大能力了,使用spring mvc提供的@CrossOrigin注解就能轻松解决
**注:**这要求你的spring必须是5.x系列才能使用!!!!
@RequestMapping("/loadData")
@ResponseBody
@CrossOrigin
public Object loadData() {
String msg = "Hello JSONP";
return msg;
}
也可以添加参数,进一步限制允许跨域请求的范围:
@RequestMapping("/loadData")
@ResponseBody
@CrossOrigin(origins = "http://localhost:8080", methods = {RequestMethod.POST, RequestMethod.GET})
public Object loadData() {
String msg = "Hello JSONP";
return msg;
}
@CrossOrigin不仅支持在方法上添加,还支持在Controller类上添加,代表该Controller下面所有方法都支持跨域请求。
实际上底层的实现原理就是通过添加以下响应头实现的:
resp.setHeader("Access-Control-Allow-Origin", "*")
当然这种是所有的跨域请求都放行,不是很安全,如果你需要只让某些网络访问,那么可以将
*
替换为具体的请求。