由于公司的新项目决定使用java 与angularjs进行开发,前后端分离,因此我需要对前端的请求框架进行搭建。接触angularjs是在上一个ionic的项目,感觉angularjs的mvc架构非常出色,尤其对大型项目很有好处。
angularjs的请求是通过ajax的,在请求过程中,有一个很麻烦的问题,那就是跨域。在这次的项目中,打算在请求的http header中加入自token进行身份验证,结果遇到了麻烦。于是现在把解决方案写下来,希望能给自己留一个记忆,并希望能够帮助遇到问题的小伙伴。
首先,任何请求都需要在http header中加入token,我了解到了angularjs里的一个很重要的机制:拦截。
可在config中加入:
config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('sessionInjector');
$httpProvider.defaults.headers.mbs-common['X-Requested-With']; }]);
factory中加入sessionInjector如下:
testService.factory('sessionInjector', ['$localStorage','HOST', function($localStorage,HOST)
{ var sessionInjector = {
request:
function(config) {
config.headers = config.headers || {};
if(config.url.indexOf(HOST.URL)>=0) {
if ($localStorage.u && $localStorage.u.token) {
config.headers.token = $localStorage.u.token;
}
}
return config;
}
};
return sessionInjector;}]);
代码写好,跑起来一看,token已经成功加入到http header中,很好!但是请求出错了!报:No 'Access-Control-Allow-Origin' 错误。看到这个报错,我的第一反映是跨域问题。但是我前一个ionic+angularjs项目在java端已经解决了跨域问题了,而这个新的项目用的框架与上个项目几乎一样的,为何还会跨域呢?我感觉自己对跨域的理解不够彻底,于是又开始漫长的搜索,各种google,stackoverflow。终于找到了跨域的原因,wiki上说得很清楚:https://en.wikipedia.org/wiki/Cross-origin_resource_sharing 。
核心问题在于跨域时会先进行一个OPTIONS请求,如果成功了才会进行GET或POST请求。于是马上通过chrome查看OPTIONS请求的结果,果然OPTIONS报403错误。
看到OPTIONS请求的403错误,我就蒙了!怎么回事呢,我明明在java代码中写了一个CorsFilter,并且我也加入了跨域的允许代码:
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpRequest = (HttpServletRequest) request;
httpResponse.setContentType("text/html;charset=UTF-8");
httpResponse.setHeader("Access-Control-Allow-Origin",httpRequest.getHeader("Origin"));
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
httpResponse.setHeader("Access-Control-Max-Age", "0");
httpResponse.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,authorization,mbs_token");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("XDomainRequestAllowed","1");
chain.doFilter(request, response);
网上说:Access-Control-Allow-Origin不能用"*",而是需要指定请求的域名:httpRequest.getHeader("Origin")
然后我再跑了一次,查看后台日志,发现CorsFilter根本就没有跑!
于是又经过了各种google与stackoverflow,转眼两天下来了,还是无果!心里真是有说不出的郁闷!!!
然后,我怀疑是不是tomcat层面拦截了OPTIONS请求,于是看了一下TOMCAT的请求日志,发现果然有拦截记录,可是不应该TOMCAT层拦截的呀!于是我做了一个HELLO WORD 发现跨域是没问题的,于是放弃这个想法,再继续找原因!
最后,我怀疑是不是Web.xml有问题,这个web.xml有部分是直接复制人家配置好的,没有好好去分析各个节点,于是我一句一句核对,终于发现一个关键地方:
<security-constraint>
<web-resource-collection>
<web-resource-name>SSL</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint></auth-constraint>
</security-constraint>
没错,就是这里!:
<http-method>OPTIONS</http-method>
我立即把这段文字删除了,然后跑了一下,奇迹出现了,跨域解决了!!!当时心里真是无比兴奋,从开始解决到完工,我整整花了三天,而且后两天是双休!
于是我花时间了解一下这个关键的<security-constraint>,网上对于这个节点的说明比较少,经过google的查阅发现:
web.xml中<security-constraint> 的子元素 <http-method> 是可选的,如果没有 <http-method> 元素,这表示将禁止所有 HTTP 方法访问相应的资源。
子元素 <auth-constraint> 需要和 <login-config> 相配合使用,但可以被单独使用。如果没有 <auth-constraint> 子元素,这表明任何身份的用户都可以访问相应的资源。也就是说,如果 <security-constraint> 中没有 <auth-constraint> 子元素的话,配置实际上是不起中用的。
<security-constraint> 是java servlet的安全配置,
可参考:http://openhome.cc/Gossip/ServletJSP/DeclarativeSecurityBasic.html
关键的一句:如果加入了 <auth-constraint> 子元素,但是其内容为空,这表示所有身份的用户都被禁止访问相应的资源。
其实在解决的过程中我也注意到过:<http-method>OPTIONS</http-method>,当时我的理解是,允许这个求请方法!
总结:任何技术疑问都不能马虎,要彻底理解才行啊!