一、背景

在公司空暇时间,笔者使用 AngularJS + SSM 方案编写一套权限控制系统。由于采用的是前后端分离的思想,前端页面服务启动的端口和后端服务启动端口不一致导致请求跨域问题的出现。在此,写下解决问题的流程。

二、基础知识

2.1 什么是同源

URL 由协议、域名、端口和路径组成,如果两个 URL 的协议、域名和端口相同,则表示它们同源。

2.2 什么是同源策略

浏览器的同源策略,限制了来自不同源的 document 或脚本,对当前 document 读取或设置某些属性。其目的是为了保证用户信息的安全,防止恶意的网站窃取数据。

另外,同源策略只对网页的 HTML 文档做了限制,对加载的其他静态资源如 javascript、css、图片等仍然认为属于同源。

2.3 什么是跨域

跨域,指的是浏览器不能执行其他网站的脚本。同源策略规定,AJAX 请求只能发给同源的网址,否则就报错。

跨域请求通常不会携带 cookies 信息,为了能让跨域请求带上 cookies,需要在前端设置:

1
xhr.withCredentials = true

这个配置需要后端的配合,需要在服务器端在响应是带上 Access-Control-Allow-Credentials , 同时它的值必须为 true:

1
Access-Control-Allow-Credentials: true

设置 withCredentials 为true 后, 在与服务器进行通信时会携带这个域名下的所有 cookies。

建议读者先浏览文章末尾提供的参考资料进一步了解跨域相关的内容,再结合本文案例思考和理解

三、解决案例

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

笔者采用 CORS 方案解决问题。

3.1 前端页面设置

3.1.1 设置请求参数

在 ajax 请求参数中添加 2 个参数设置:

1
2
xhrFields: { withCredentials: true }
crossDomain: true

3.1.2 源码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$.ajaxSetup({
dataType: "json",
cache: false,
xhrFields: { withCredentials: true },//设置后,请求会携带cookie
crossDomain: true,
complete: function(xhr) {
if (xhr.responseJSON) {
if (xhr.responseJSON.code == 401) {
layer.msg(xhr.responseJSON.msg);
setTimeout(function() {
window.location.href = "login.html";
}, 1500);
}
} else {
layer.msg(xhr.responseText);
}
}
});

3.2 后端服务器设置

3.2.1 设置跨域请求过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class SimpleCORSFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request=(HttpServletRequest)req;
// 处理简单请求
// 跨域请求默认不携带cookie,如果要携带cookie,需要设置下边2个响应头
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));// 必选,所有有效的跨域响应都必须包含这个请求头, 没有的话会导致跨域请求失败
response.setHeader("Access-Control-Allow-Credentials", "true");//可选,此处设置为true,对应前端 xhr.withCredentials = true;
//处理非简单请求
// 非简单请求:浏览器会发送两个请求, 第一个请求(成为预检请求)会像服务器确定是否接受这个跨域请求, 第二个才是真正的发出请求. 浏览器自动的处理这两个请求, 同时预检请求也是可以被缓存的, 而不用每次请求都需要发送预检请求.
// 预检请求是在实际的请求发出前先向服务器确认是否能够处理这个请求. 服务器应该检查上边两个请求头的值, 来判断这个请求是否有效.
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE");// 必选
response.setHeader("Access-Control-Allow-Headers",
"Origin,No-Cache,X-Requested-With,If-Modified-Since," +
"Pragma,Last-Modified,Cache-Control,Expires,Content-Type,X-E4M-With,userId,token");

response.setHeader("Access-Control-Max-Age", "0");// 可选,在每个请求前面都发送一个预检请求是很浪费资源的, 这个值允许你设置预检请求的缓存时间, 单位是秒.
response.setHeader("XDomainRequestAllowed","1");
chain.doFilter(req,resp);
}

public void init(FilterConfig filterConfig) {}

public void destroy() {}
}

3.2.2 配置 web.xml 文件

1
2
3
4
5
6
7
8
<filter>
<filter-name>cors</filter-name>
<filter-class>com.light.system.web.filter.SimpleCORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

四、参考资料