写在前面。前端分离其实有两种:开发阶段使用dev-server,生产阶段打包成静态文件整体放到后端工程中。dev-server用于开发阶段,生产阶段打包成静态文件放在单独的静态资源服务器中,比如nginx。两种方案最大的区别在于制作阶段。由于第一种方案前后端本质上是在同一个服务中,根本不存在跨域,配置cas的坑也比较少。对于第二种方案,我们一般使用nginx反向代理来完成跨域,cas配置会有很多坑。为了后面分析方便,我们将上述两种方案分别称为“前后端分离A”和“前后端分离B”。访问某个URL,Postman测试接口,这些行为其实都是发起HTTP请求,不会出现跨域问题。2、AJAX(XMLHttpRequest)请求:这是浏览器内部的XMLHttpRequest对象发起的请求。浏览器会禁止它发起跨域请求,主要是防止跨站脚本伪造攻击(CSRF)。难点分析前后端分离、跨域、CAS这三种技术,单独使用并不难,甚至两种一起使用。下面一一列举:前后端分离(AB)+跨域前后端分离A+CAS(因为方案A根本没有跨域)前后端分离B+跨域+CAS前后端分离(AB)+跨域最简单,只有跨域,没有CAS,常见的CORS,反向代理,JSONP可以解决前后端分离A+CAS的坑:CAS认证过期,可能会问莫名其妙的跨域错误,你刚才不是说A方案根本没有跨域问题吗?其实这个跨域错误不怪我们后端,而是CAS这边的后端,我来详细解释一下。一般情况下,CAS认证成功后,浏览器会设置一个来自CAS的cookie来维持与CAS的会话。之后的每一次请求,不管是ajax请求还是http请求,都会带上这个cookie。而我们自己的后台服务器也会有一个CASAuthorizationfilter,将没有经过CAS认证的请求重定向到CAS的登录页面。因为我们在这次请求中带上了cas的cookie,所以请求顺利通过了filter到controller层,然后返回了数据。但是考虑这样一种情况,今天你打开浏览器,访问一个你新建的cms系统的url,然后跳转到cas登录页面,正常登录,正常使用。然后第二天早上来了,因为你昨天没有关闭页面,直接点了一个查询按钮,结果报错。你打开浏览器控制台,发现报跨域错误。这里有两个困惑:他为什么要跨域喵喵叫?我们的前端和后端都命名并部署在同一域中的服务器上。cas认证失败后为什么不自动跳转到cas登录页面?但是我之前在浏览器中直接输入cms系统的地址,浏览器可以直接跳转到cas登录页面,因为我没有通过认证。这次为什么不呢?ajax如何处理302?为什么会跨域:想想这样一个过程:你第二天早上来,点击一个查询按钮,发起一个ajax请求,在请求中带上一个过期的cookie,然后请求被拒绝后端的casfilter拦截发现无效,让302跳转到cas登录界面。在这个过程中,你之前发起的ajax请求实际上是重定向到了cas的login.html页面(这只是表象,本质后面会说到)。你相当于发起ajax请求去请求一个html文件,但是cas服务器没有配置跨域,出于安全原因不能配置跨域,所以你的ajax请求还没来得及请求数据,以及你被浏览器认为是跨域的,因为你确实是在请求cas服务器的静态资源。退一步说,即使cas服务器配置了跨域,虽然你点击查询按钮不会报跨域错误,但是还是不能自动跳转到cas登录页面,因为这个login.html直接作为你ajax中的回调参数返回,浏览器不会给你跳转。为什么不能跳转:首先,当你打开浏览器输入cms系统的地址访问时,发起的是HTTP请求,不存在跨域问题。所以你的HTTP请求被后端的filter重定向到了cas的login.html,这个过程是没有问题的。并且当你点击查询按钮时,会发起ajax请求,无法重定向(具体原因见下文)。ajax在302行为的本质,??当你点击查询按钮时,发起ajax请求,请求被后端过滤器拦截,通知你302跳转到登录页面。这时候浏览器会先感知ajax请求的302状态,并访问ajax要重定向到的地址,然后将访问结果(实际上是整个login.html页面)发送给你的ajax成功回调函数,所以这个回调函数的参数其实就是整个login.html页面。而且,只有浏览器将html放入ajax的成功回调函数中,ajax才会真正回调。之前的302状态是ajax感知不到的,当然获取不到,所以想通过ajax判断状态是否为302。此外,手动location.href到登录页面的方案是不可接受的。事实上,它看起来像你的ajax请求直接到login.html页面。另外在实际的cas跳转过程中,在ajax成功回调之前,你的ajax操作被浏览器认为是跨域的,所以你根本没有机会回调成功,所以无法获取status或者没用的login.html。好了,疑惑解决了,该说说解决方法了:我们要实现的是:cookie失效时,点击查询按钮后,能够自动跳转到cas登录页面。解决方案有很多,但都依赖于两点:使用HTTP请求而不是Ajax请求跳转到登录页面使用200而不是302通知ajax当前请求状态举几个例子:1、错误的解决方法:试试拦截ajax响应,然后判断响应的状态是否为302,如果是302,手动用location.href跳转到cas登录页面,但是这样不行,因为我们根本获取不到302状态.2.后端必须配合。后端需要添加一个额外的过滤器和一个控制器。给它起个名字吧,我们叫它ValidateFilter和ValidateController。ValidateFilter只过滤那些需要被cas拦截的请求,判断doFilter中HttpServletRequest的状态,看这个请求中是否能获取到当前用户名,如果能获取到,说明认证没有问题,让这个请求继续往下链.doFilter,如果获取不到,说明认证无效(因为filter不能直接返回,所以需要一个ValidateController),我们request.dispatch这个请求到redirect方法ValidateController(自己写的),让这个redirect方法返回一个结果,在result中设置一个flag,比如code:xxx。然后前端尝试在ajax的响应之前获取响应的结果,看结果的code是不是xxx。如果是这样,那么位置。结束界面,然后让后台跳转到前台页面。为什么服务不能直接写前端呢?因为我们不仅要和casserver保持session,还要和自己的backend保持session。如果后台不回调,后台就不知道我们的登录状态。例如://frontend:if(result.code===xxx){location.href="http://cas.server.com/login?service=http://后端服务器地址/redirect/to/frontend?currentPath=当前页面路径"//currentPath是登录后返回当前页面}//后端过滤伪代码:voiddoFilter(request,response,chain){if(usernameinrequest){chain.doFilter()}elseif(request.uri=='/redirect/to/caslogin'){chain.doFilter()}else{request.dispatch("/redirect/to/caslogin")}}//后端控制器伪代码//用于接受认证失败的过滤器的请求@path("/redirect/to/caslogin")StringredirectToCasLogin(request,response){return{"code":xxx}}//登录后使用回调@path("/redirect/to/frontend")StringredirectToFrontend(request,response){Stringpath=currentPathparameterinrequest.sendRedirect(path)}//另外这个controller一定不能被validateFilter过滤,因为如果这个controller是也过滤了,就会陷入cas验证的死循环。3、和2类似,只是在location.href中直接写location.href="http://后台服务器地址/redirect/to/caslogin?currentPath=当前页面路径"。这个时候我们直接请求后端接口/redirect/到/caslogin,他先是被validateFilter拦截了,但是因为一个if判断,直接是doFilter,然后请求来到了cas的filter,因为有没有登录,过滤器会自动拼接我们配置的casserverName+当前请求的uri,同一个urllike"http://cas.server.com/login?service=http://backendserveraddress/redirect...路径”将形成。前后端分离B+跨域+CAS写不下去了。总之,注意:保持cookie的域一致。对于nginx来说,如果从www.a.com/代理到www.b.com/api,那么形成的cookie的域就是/api,浏览器发起时只能携带/域的cookie请求,因此cookie丢失并且会话无效。可以通过nginx配置,将/api域下的cookies全部放入/中解决。为了避免额外的麻烦,代理前后的URL最好保持一致,即有/api前缀,或者没有。对于浏览器来说,ajax发起的cookie是和发起请求的主机域名严格相关的。不同的域名有不同的cookie,所以如果出现,说明你已经登录了,但是如果你在这里发起ajax请求,后台还是无法识别你的登录状态,可能是你请求的域名启动不一致。也就是说,你请求后端接口的时候,你用的是www.a.com,cas登录成功后要回调的接口变成了www.b.com,所以你的cas登录状态cookie是附加在www上的.b.com的域名起来了,然后当你向www.a.com发起请求时,发现你根本带不来cas的cookie,因为域不一样。这种情况一般发生在使用反向代理的时候。前端向代理服务器www.a.com发起Ajax请求,代理服务器向www.b.com发起请求。这时候很容易造成域名不一致。请注意这一点。另外,针对目前前后端分离部署的情况,在location.href中,服务的回调接口不能直接写入后端地址(相当于www.b.com),而是代理服务器访问www.b.com应该写成www.a.com,这样才能保持cookie域的一致性!!!!
