我们这个项目前端使用antD,antD是采用React封装的一套组件库,目前开源http://ant.design/,所有组件都是拿来即用,大大缩短了开发周期,强烈推荐。React是单页面应用,通过ajax与后台通信,而antD调试部署在8000端口,后台又是运行在另一个端口,前后台通信跨域。AJAX跨域一般有两种解决方法:CORS(跨域资源共享)和JSONP。
先来看看JSONP,本质原理利用script标签src属性可以跨域的特性,我们自己也可以去实现JSONP,动态添加删除script标签:
function loadJs() { var script = document.createElement("script"); script.src = "http://xxxxxx/get/req"; document.body.appendChild(script); script.onload = function() { callback(); document.body.removeChild(script); } }
在AJAX中使用JSOP:
$.ajax({ url: 'http://xxxxxx/get/req', cache: false, type: 'post', jsonp:'callback', dataType:'jsonp', success: function (result) { //处理结果的过程 }, error: function (XMLHttpRequest) { //出错回调处理 } });
相应的后台代码:
/** * 测试 * * @throws IOException */ @RequestMapping("/get/req") @ResponseBody public void getData(HttpServletRequest req, HttpServletResponse rep) throws IOException { Map<string, object=""> result = null; result = manager.getDatas(); String callback = req.getParameter("callback"); String json = JSONObject.toJSONString(result); rep.setContentType("text/javascript"); rep.setCharacterEncoding("utf-8"); PrintWriter out = rep.getWriter(); out.print(callback + "(" + json + ")"); }
CORS分为简单请求和非简单请求,非简单请求的请求方法不是POST、GET、HEAD,或者header中包含一些特殊请求头,以及Content-Type不是application/x-www-form-urlencoded、multipart/form-data、text/plain的请求。向后台发送请求时会多发送一次option请求,并且不能携带cookie,这个后面再说。CORS只需服务器端在每次响应中设置一些响应头即可,将这些操作放在Filter中实现低耦合:
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse rep = (HttpServletResponse) response; HttpServletRequest req = (HttpServletRequest) request; rep.addHeader("Access-Control-Allow-Origin", req.getHeader("Origin")); rep.setHeader("Access-Control-Allow-Credentials", "true");//跨域携带cookie rep.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");//非简单请求 rep.setContentType("application/json; charset=utf-8"); rep.setHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE");//非简单请求 chain.doFilter(request, rep); }
我们的项目需要接入统一登录中心,SSO大家也知道,每次请求来了之后SSOClient判断是否携带token,过期或者没有token会重定向到登录页面让用户进行登录。这里出现了一个问题,前端向后台发送AJAX请求,在用户没有登录时并不能正常重定向到登录中心,而是出现前台到登录中心页面的XMLHttpRequest跨域错误提示。但是通过浏览器直接输入后台请求URL地址,在用户没有登录时,会重定向到登录中心页面。对于这个问题的产生一开始没有头绪,进行了多种尝试之后定位到了原因。
这里涉及到浏览器是如何处理AJAX请求重定向,当服务器将302响应发给浏览器,浏览器并不是直接进行AJAX回调处理,而是先执行302重定向,从Response Headers中读取Location信息,然后向Location中的Url发出请求,在收到这个请求的响应后才会进行AJAX回调处理。antD是个单页面应用,前后台交互通过AJAX的方式,所以当SSOFilter拦截ajax请求之后,浏览器会首先重定向到登陆中心,产生了前台到登录中心的跨域AJAX请求,当浏览器发现响应头信息没有包含Access-Control-Allow-Origin字段,从而抛出XMLHttpRequest cannot load的错误。
CORS请求默认不发送Cookie和http认证信息,如果要把Cookie发送到服务器,一方面需要服务器同意,指定Access-Control-Allow-Credentials响应头,另一方面必须在AJAX中打开withCredentials属性,$.ajaxSetup方法设置AJAX请求的默认参数选项,多个AJAX请求时,不用为每一个请求配置请求的参数:
$.ajaxSetup({ xhrFields: { withCredentials: true }, })