前端应用为静态站点且部署在http://web.xxx.com域下,后端应用发布rest api并部署在http://api.xxx.com域下,如何使前端应用通过ajax跨域访问后端应用呢?这需要使用到cors技术来实现,这也是目前最好的解决方案了。
[cors全称为cross origin resource sharing(跨域资源共享),服务端只需添加相关响应头信息,即可实现客户端发出ajax跨域请求。]
cors技术非常简单,易于实现,目前绝大多数浏览器均已支持该技术(ie8浏览器也支持了),服务端可通过任何编程语言来实现,只要能将cors响应头写入response对象中即可。
下面我们继续扩展rest框架,通过cors技术实现ajax跨域访问。
首先,我们需要编写一个filter,用于过滤所有的http请求,并将cors响应头写入response对象中,代码如下:
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 chain) throws ioexception, servletexception {
httpservletrequest request = (httpservletrequest) req;
httpservletresponse response = (httpservletresponse) res;
if (stringutil.isnotempty(alloworigin)) {
list<string> alloworiginlist = arrays.aslist(alloworigin.split(","));
if (collectionutil.isnotempty(alloworiginlist)) {
string currentorigin = request.getheader("origin");
if (alloworiginlist.contains(currentorigin)) {
response.setheader("access-control-allow-origin", currentorigin);
}
}
}
if (stringutil.isnotempty(allowmethods)) {
response.setheader("access-control-allow-methods", allowmethods);
}
if (stringutil.isnotempty(allowcredentials)) {
response.setheader("access-control-allow-credentials", allowcredentials);
}
if (stringutil.isnotempty(allowheaders)) {
response.setheader("access-control-allow-headers", allowheaders);
}
if (stringutil.isnotempty(exposeheaders)) {
response.setheader("access-control-expose-headers", exposeheaders);
}
chain.dofilter(req, res);
}
@override
public void destroy() {
}
}
以上corsfilter将从web.xml中读取相关filter初始化参数,并将在处理http请求时将这些参数写入对应的cors响应头中,下面大致描述一下这些cors响应头的意义:
access-control-allow-origin:允许访问的客户端域名,例如:http://web.xxx.com,若为*,则表示从任意域都能访问,即不做任何限制。
access-control-allow-methods:允许访问的方法名,多个方法名用逗号分割,例如:get,post,put,delete,options。
access-control-allow-credentials:是否允许请求带有验证信息,若要获取客户端域下的cookie时,需要将其设置为true。
access-control-allow-headers:允许服务端访问的客户端请求头,多个请求头用逗号分割,例如:content-type。
access-control-expose-headers:允许客户端访问的服务端响应头,多个响应头用逗号分割。
需要注意的是,cors规范中定义access-control-allow-origin只允许两种取值,要么为*,要么为具体的域名,也就是说,不支持同时配置多个域名。为了解决跨多个域的问题,需要在代码中做一些处理,这里将filter初始化参数作为一个域名的集合(用逗号分隔),只需从当前请求中获取origin请求头,就知道是从哪个域中发出的请求,若该请求在以上允许的域名集合中,则将其放入access-control-allow-origin响应头,这样跨多个域的问题就轻松解决了。
以下是web.xml中配置corsfilter的方法:
<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>
完成以上过程即可实现ajax跨域功能了,但似乎还存在另外一个问题,由于rest是无状态的,后端应用发布的rest api可在用户未登录的情况下被任意调用,这显然是不安全的,如何解决这个问题呢?我们需要为rest请求提供安全机制。