写在最前

前后端分离其实有两类:

  1. 开发阶段使用dev-server,生产阶段是打包成静态文件整个放入后端项目中。
  2. 开发阶段使用dev-server,生产阶段是打包成静态文件放入单独的静态资源服务器中,如nginx。

这两种方案最大的区别就是生产阶段。由于第一种方案前端和后端本质在同一个服务中的,所以压根就没有跨域,配置cas的坑比较少。而第二种方案我们一般使用nginx反向代理完成跨域,配置cas的坑会很多。为了后面分析方便,我们分别称上述两种方案为『前后端分离A』和『前后端分离B』

请求也分为两类:

1.HTTP请求:像浏览器地址栏发起的请求、浏览器自发的访问某个网址、Postman测试接口,这些行为其实都是发起的HTTP请求,不会有跨域问题。

2.AJAX(XMLHttpRequest)请求:这是浏览器内部的XMLHttpRequest对象发起的请求,浏览器会禁止其发起跨域的请求,主要是为了防止跨站脚本伪造的攻击(CSRF)。

难点分析

前后端分离、跨域、CAS这三项技术单独使用起来,甚至拿其中两个出来一起使用,难度都不大,下面来列举一下:

  1. 前后端分离(AB)+跨域
  2. 前后端分离A+CAS(因为A方案根本就没有跨域这一说)
  3. 前后端分离B+跨域+CAS

前后端分离(AB)+跨域

这个最简单,只有跨域,没有CAS,常见的CORS、反向代理、JSONP都可以解决

前后端分离A+CAS

坑:CAS认证过期,莫名出现跨域错误的问题

可能有人会问,刚才不是说方案A压根就没有跨域问题吗?其实,这个跨域错误不怪我们的后端,而是怪CAS那边的后端,待我详细说来。

正常情况下,CAS认证成功后,浏览器会设置好一个来自CAS的Cookie以维持与CAS的Session。之后每次请求,无论是ajax请求还是http请求,都会带上这个cookie。而我们自己的后端服务器也会有一个CAS Authorization的过滤器,把没有CAS认证过的请求重定向到CAS的login页面。因为本次请求我们带了cas的cookie,所以请求顺利通过filter来到controller层,进而返回数据。

但是考虑这样一个情况,今天你打开你的浏览器,访问一个你们新做的cms系统的网址,然后跳到cas login页面,正常登陆,正常使用。然后来到第二天早上,因为昨天的页面你没关,你直接点了一个查询按钮,结果报错了。你打开浏览器控制台,竟然发现报了一个跨域的错误。这里有两处困惑:

  1. 为什么他喵的会跨域呢?我们前后端命名部署在一台服务器上,是同域的啊。
  2. 为什么cas认证失效后,没有自动跳到cas登录页呢?可是我之前直接在浏览器输入cms系统的地址时,因为没认证过,浏览器是能直接跳到cas登录页的,为什么这次不行呢?
  3. ajax到底怎么处理302的

为什么会跨域:
想想一下这样的一个流程:第二天早上你来,点击一个查询按钮,发起了ajax请求,请求中带上了一个已经失效的cookie,然后请求被后端cas filter拦截,发现已失效,让后302跳转到cas login界面。在这个过程中,你之前发起的ajax请求其实被redirect到了cas的login.html页面(这只是表象,本质后面会提到)。你相当于发起ajax请求去请求一个html文件下来,然而cas的服务器并没有配置跨域,为了安全考虑也不能配置跨域,所以你的ajax请求还没来得及请求下来数据,你就被浏览器认为是跨域了,因为你的确在请求cas服务器的一个静态资源。

退一万步说,就算cas服务器配置了跨域,虽然你点击查询按钮的行为不会报跨域错误了,但你依然不能自动跳转到cas login页面,因为这个login.html直接当做你ajax的success中的回调参数回来了,浏览器是不会帮你跳转的。

为什么不能跳转:
首先,你打开浏览器输入cms系统的地址去访问的时候,发起的是HTTP请求,是不存在跨域问题的。因此你的HTTP请求被后端的filter给redirect到了cas的login.html,这个流程是没问题的。而你点击查询按钮,发起的是ajax请求,是没法跳转的(具体原因见下方文字)

ajax在302中的行为本质
当你点击查询按钮,发起的是ajax请求,请求被后端filter拦截,并告知你302跳转到login页,此时浏览器首先会感知到这次ajax请求的302状态,并替ajax去访问要跳转到的地址,然后将访问的结果(其实就是整个login.html页面)返回到你的ajax的success回调函数中,因此这个回调函数的参数其实就是整个login.html的页面。并且,直到浏览器把html放到ajax的success回调函数后,ajax才会真正的回调,之前的302状态ajax是感知不到的,当然也获取不到,所以想通过ajax判断status是否是302,进而手动location.href到login页的方案是不行的。

其实,这么看起来就像是你的ajax直接请求到了login.html页面。
另外,在实际cas跳转的过程中,在ajax的success回调之前,你的ajax操作就被浏览器认为是跨域了,所以你压根就没机会回调success,也因此获取不到status状态或者那个没卵用的login.html。

好了,疑惑解决完了,该说说解决方案了:

我们要实现的就是:在cookie失效时,点击查询按钮后,能自动跳转到cas登录页。
方案很多,但都靠一下两点:
用HTTP请求替代Ajax请求去跳转到登录页
用200代替302告知ajax当前请求的状态

举几个例子:

1、错误方案:设法拦截ajax的response,然后判断response的status是否是302,如果是302就手动location.href跳到cas登录页,但是这样是不行的,因为我们根本获取不到这个302状态。
2、必须要后端配合,后端需要额外加1个filter和1个controller, 起个名字吧,就叫ValidateFilter和ValidateController吧。

ValidateFilter只过滤那些需要被cas拦截的请求,在doFilter里面判断HttpServletRequest的状态,看看这个request里能不能获取到当前用户名,如果能获取到,代表认证没问题,让这个请求继续往下走chain.doFilter,如果不能获取到,代表认证失效了(因为filter不能直接返回,所以我们需要一个ValidateController),我们request.dispatch这个请求到ValidateController的redirect方法中(自己写的),让这个redirect方法返回一个result,result中设置一个标志,比如给code:xxx。

然后前端设法在ajax的response之前获取response的result,看看result的code是否为xxx,如果是,那就location.href跳转到cas登录页即可,其中service参数写cas登陆之后要回调的后端接口,然后让后端去跳转到前端页面。

为什么不能直接service写前端?
因为我们不仅要跟cas服务器维持session,还要跟我们自己的后端维持session,如果不回调后端,后端就不会感知到我们的登录状态了。

比如:

//前端:
if(result.code === xxx) {
location.href = "http://cas.server.com/login?service=http://后端服务器地址/redirect/to/frontend?currentPath=当前页面路径"
//currentPath是为了login之后再调回当前页面
}
//后端 filter 伪代码:
void doFilter(request, response, chain) { if(request中有用户名) {
chain.doFilter()
} else if(request.uri == '/redirect/to/caslogin') {
chain.doFilter()
} else {
request.dispatch("/redirect/to/caslogin")
}
}
//后端 controller 伪代码
// 用来接受filter过来的那些认证失效的请求
@path("/redirect/to/caslogin")
String redirectToCasLogin(request, response) {
return {
"code": xxx
}
}
// 用来在login之后回调用
@path("/redirect/to/frontend")
String redirectToFrontend(request, response) {
String path = request中的currentPath参数
request.sendRedirect(path)
} // 另外,这个controller一定不要被validateFilter过滤,因为如果这个controller也要被过滤,那就陷入cas验证的死循环了。

3.和2类似,但是location.href中直接写

location.href = "http://后端服务器地址/redirect/to/caslogin?currentPath=当前页面路径"

此时我们直接请求后端接口/redirect/to/caslogin,他首先被validateFilter拦截,但是因为有一个if判断,他被直接doFilter,然后请求来到了cas的Filter,因为没登录,该filter会自动拼接我们配置的cas serverName+当前请求的uri,同样会形成
"http://cas.server.com/login?service=http://后端服务器地址/redirec...径"这样的url。

前后端分离B+跨域+CAS

写不动了,总之要注意:要保持cookie的域一致

对于nginx,如果从 www.a.com/ 代理到 www.b.com/api,那么形成的cookie的域是会是/api,而浏览器发起请求时只能携带/域的cookie,所以导致cookie丢失,session失效。可以通过nginx配置,把/api域下的cookie都放到/即可解决。
为了避免额外的麻烦,最好保持代理前后url一致吧,即都有一个/api前缀,或者都没有。

对于浏览器,发起的ajax所带的cookie是发起请求的host域名有严格关系的,不同的域名带不同的cookie,所以如果出现,你明明已经登陆了,但是在此发起ajax请求,后端还是识别不出来你的登录状态,那就可能是你发起的请求的域名不一致了。也就是说,你去请求后端接口的时候用www.a.com,结果cas登陆成功后的要回调的接口成了www.b.com,这样你的cas登录状态的cookie就附着在www.b.com的域名上了,然后当你再发起www.a.com的请求的时候,发现你根本带不上cas下来的cookie,因为域不同。
这种情况通常发生在反向代理的时候,前端发起ajax请求代理服务器www.a.com,代理服务器发起请求到www.b.com,这时候就容易导致域名不一致,请一定要注意这点。

另外,对于当前前后端分开部署的情况,location.href中,service的回调接口不能直接写后端地址(相当于www.b.com),而应该写www.a.com,让代理服务器去访问www.b.com,这样才能保持cookie的域的一致性!!!!

前后端分离下的CAS跨域流程分析的更多相关文章

  1. vue+springboot前后端分离实现单点登录跨域问题处理

    最近在做一个后台管理系统,前端是用时下火热的vue.js,后台是基于springboot的.因为后台系统没有登录功能,但是公司要求统一登录,登录认证统一使用.net项目组的认证系统.那就意味着做单点登 ...

  2. nodeJS(express4.x)+vue(vue-cli)构建前后端分离详细教程(带跨域)

    好想再回到大学宿舍,当时床虽小,房随小,但是心确是满的 ----致  西安工程大学a-114舍友们 转载请注明出处:水车:http://www.cnblogs.com/xuange306/p/6185 ...

  3. 使用 Nginx 部署前后端分离项目,解决跨域问题

    前后端分离这个问题其实松哥和大家聊过很多了,上周松哥把自己的两个开源项目部署在服务器上以帮助大家可以快速在线预览(喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了 ...

  4. Spring Cloud 前后端分离后引起的跨域访问解决方案

    背景 Spring Cloud 微服务试点改造,目前在尝试前后端分离. 前台A应用(本机8080端口),通过网管(本机8769端口)调用后台应用B(本机8082端口).应用C发布的http服务.. A ...

  5. ASP.NET WebApi+Vue前后端分离之允许启用跨域请求

    前言: 这段时间接手了一个新需求,将一个ASP.NET MVC项目改成前后端分离项目.前端使用Vue,后端则是使用ASP.NET WebApi.在搭建完成前后端框架后,进行接口测试时发现了一个前后端分 ...

  6. 解决前后端分离后的Cookie跨域问题

    一. 前端Ajax关键配置 $.ajax({ type: "post", url: xxx, data: xxx, contentType: 'application/json', ...

  7. lf 前后端分离 (3) 中间建跨域

    一.关于中间建跨域 为了减少跨域代码冗余,采用中间件 from django.utils.deprecation import MiddlewareMixin class CorsMiddleware ...

  8. 08 Django REST Framework 解决前后端分离项目中的跨域问题

    01-安装模块 pip install django-cors-headers 02-添加到INSTALL_APPS中 INSTALLED_APPS = ( ... 'corsheaders', .. ...

  9. 前后端分离下用jwt做用户认证

    0 前后端分离下的用户信息认证 前端使用Vue+axios,后端使用SpringBoot+SpringSecurity. 为了解决http无状态的问题,我采用jwt(json web token)保存 ...

随机推荐

  1. 2019 年百度之星·程序设计大赛 - 复赛 Diversity (树形DP)

    题目:https://vjudge.net/contest/325352#problem/A 题意:给你一棵树,每个点给一个区间,可以选区间里面任何一个数,然后问怎么安排得到最大边两点之间的差值和,求 ...

  2. centos7中yum安装lamp环境

    一.准备工作 1.1 环境 操作系统:centos7(CentOS-7-x86_64-Minimal-1708) 硬件:(这个根据项目运行和配置建议设置,一般我先配个1核1G) 1.2 关闭selin ...

  3. CSS动画之旋转魔方轮播

    很久没有回头来复习CSS方面的知识了, 正好又到了月底写文章的deadline......所以这次选择了详细巩固一下CSS3动画有关的知识点,因为之前只是用过一些属性并没有深究细节. 在我自己写完这篇 ...

  4. 2018 CCPC 吉林站 H Lovers || HDU 6562 (线段树哦)

    http://acm.hdu.edu.cn/showproblem.php?pid=6562 题意: q次操作 1.将第l~r个数的左边和和右边都加上一个数d, 使得这个数变成 dsiddsid的形式 ...

  5. WCF身份验证之用户名密码认证

    WCF支持多种认证技术,例如Windowns认证.X509证书.Issued Tokens.用户名密码认证等,在跨Windows域分布的系统中,用户名密码认证是比较常用的,要实现用户名密码认证,就必须 ...

  6. vue分页练习

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. Python 图片格式的转换和尺寸修改

    import cv2 import os import numpy as np from PIL import Image import shutil import sys image_size=14 ...

  8. Ubuntu下使用boost例子

    http://blog.csdn.net/dotphoenix/article/details/8459277 1. 安装boost库 sudo apt-get install libboost-al ...

  9. HTML5--浏览器全屏操作、退出全屏、是否全屏

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. jQuery基础--选择器

    2. 选择器 2.1. 什么是jQuery选择器 jQuery选择器是jQuery为我们提供的一组方法,让我们更加方便的获取到页面中的元素.注意:jQuery选择器返回的是jQuery对象. jQue ...