浏览器的同源策略限制默认情况下前端页面和后端服务在不同服务器(域名、端口不一样)时,前端页面js无法请求到后端接口服务,即存在跨域问题。
跨域问题解决思路
使用jsonp方式解决
使用cors解决
使用nginx代理解决
这里不讨论jsonp的方式,主要讨论cors和代理方式。
cors方式
通过服务端设置接口响应头Header允许接口被跨域请求,接口做如下设置即可:
@RequestMapping("crossorigin")
public String crossorigin(HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
return "crossorigin请求";
}
那么服务端即允许接口被跨域访问,不同源的前端页面也就可以调用该接口。
当然,这里只是单独设置了一个接口的响应头,实际项目中可以根据需要选择不同的设置方式,例如:
使用spring的注解@CrossOrigin
自定义拦截器,对每个请求的response header进行设置
spring自带的cors标签进行xml设置
web.xml中通过filter设置
我更喜欢自定义拦截器的方式对response header设置,允许接口跨域请求。
还存在的一些问题
上面操作只是解决了接口可以跨域请求问题,对于跨域请求携带cookies信息,还没有解决。因为默认前端页面ajax跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等),这也是跨域请求sessionID每次都变化的问题,下面举例说明sessionID每次变化的原因:
浏览器第一次请求服务器,cookies中没有携带sessionID,服务器会随机生成一个唯一sessionID,然后返回给浏览器,浏览器将这个sessionID保存到cookies。
正常非跨域请求,以后浏览器再次请求服务器,会将这个sessionID通过cookies发送给服务器,服务器接收到sessionID,就不会重新生成一个sessionID返回给浏览器,而是返回本次接受到的sessionID,通过这种方式来保持session会话,这样每次请求服务端时sessionID就不会变化。
因为跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等),也就是每次请求不携带cookie中数据,每次请求cookies中都不会携带sessionID,那么服务端就会认为请求是没有sessionID的第一次请求,便重新生成一个sessionID返回给浏览器。所以,每次前端页面跨源ajax请求sessionID都会变化。
要让跨域请求提供凭据,可以在ajax请求中通过将withCredentials属性设置为true,指定某个请求应该发送凭据。
这样每次跨域请求都会将cookie中的sessionID发送给服务器,sessionID就不会再改变了。
要使前端ajax跨域请求携带凭据(cookies、http认证等),需要在ajax请求中增加属性,增加下面属性
xhrFields: { withCredentials: true } 即可。
前端代码:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>session-test</title>
<script src="jquery-3.3.1.min.js" type="text/javascript"></script>
</head>
<body>
<h1>session</h1>
<script>
$(document).ready(function() {
$.ajax({
url:"http://localhost:8080/ssm-example/getSession",
xhrFields: {
withCredentials: true
},
success:function(data){
alert("sessionID = " + data);
}
});
});
</script>
</body>
</html>后端代码:
package com.ssm.example.controller;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@RequestMapping("/getSession")
@ResponseBody
@CrossOrigin()
public String getSession(HttpSession session) {
String sessionID = session.getId();
System.out.println(sessionID);
return sessionID;
}
}
当然,因为session的局限性,越来越多的项目放弃使用session的方式做登录认证,而是将需要认证的信息例如token放在请求头中,传递给服务端,因此也无需跨源请求传递请求凭证,前端页面也无需增加上面属性。
代理方式解决跨域问题
代理方式工具有多种,这里使用常用的nginx举例,nginx代理解决跨域原理其实很简单,从浏览器同源策略的限制角度考虑,通过代理方式将前端页面和后端接口代理成为同源服务(即浏览器访问前端页面和后端接口的ip主机相同、port端口相同),这样前端页面请求接口时就不会因为同源策略限制出现跨域问题。例如:
nginx配置,将前端页面代理为localhost:80地址,将后端接口代理为localhost:80/apis地址,那么浏览器就会以为前端页面和后端接口同源。
注意:因为nginx配置代理路径时,对Windows路径支持不太好,因此root路径最好使用相对路径。
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
charset UTF-8;
location / { # 这里配置前端页面代理
root html;
index index.html index.htm;
}
location /apis { # 这里配置后端接口代理
proxy_pass http://localhost:8080/;
}
}
}
前端页面JavaScript配置,经过上面nginx代理,现在前端页面地址通过 http://localhost/具体页面路径 访问,页面ajax请求接口地址也要换成nginx代理后的http://localhost/apis/具体接口地址 来访问,这样浏览器在调用的时候才会没有同源限制。
<h3 id="h3">hello crossorigin</h3>
<script type="text/javascript">
$("#h3").click(function() {
alert("开始请求");
$.ajax({
url:"http://localhost/apis/crossorigin", # 这里使用的是nginx代理后的接口路径,关键
success:function(data) {
alert("Data: " + data);
}
});
});
</script>
页面访问http://localhohst/crossorigin.html,那么页面和接口的ip都是localhost,port端口都是80,没有浏览器同源限制,解决了跨域问题,前端页面请求会携带cookies等凭证,后端接口无需针对response header做跨域调整。
代理方式应该是解决跨域问题的最优方案,开发中应该优先采用该方式处理跨域问题,当然方案的选择还是要根据实际遇到的需求变通。