主要参考
https://www.cnblogs.com/notDog/p/5252973.html
http://www.blogjava.net/xmatthew/archive/2008/07/09/213808.html
粗略的解释:
1. 客户登录 www.xn.com/1.html,(假设所有的资源,都会被filter拦截,这里会引发一些思考, 如果静态资源不拦截, AJAX的动态请求,如果在没有session的情况下, 会被跨域重定向到https://login.xn.com/login上,肯定会返回response空的情况,会给前端带来问题), 首先进入 www.xn.com应用的
AuthenticationFilter > doFilter ,从代码里看,如果
Assertion 是session里的一个验证实体, 在登录成功后,会被设置到session里.
public
final
void
doFilter(
final
ServletRequest servletRequest,
final
ServletResponse servletResponse,
final
FilterChain filterChain)
throws
IOException, ServletException {
final
HttpServletRequest request = (HttpServletRequest) servletRequest;
final
HttpServletResponse response = (HttpServletResponse) servletResponse;
final
HttpSession session = request.getSession(
false
);
// 该变量为判断用户是否已经登录的标记,在用户成功登录后会被设置
final
Assertion assertion = session !=
null
? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) :
null
;
// 判断是否登录过,如果已经登录过,进入if并且退出
if
(assertion !=
null
) { filterChain.doFilter(request, response);
return
; }
// 如果没有登录过,继续后续处理 // 构造访问的URL,如果该Url包含tikicet参数,则去除参数
final
String serviceUrl = constructServiceUrl(request, response);
// 如果ticket存在,则获取URL后面的参数ticket
final
String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());
// 研究中
final
boolean
wasGatewayed =
this
.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
// 如果ticket存在
if
(CommonUtils.isNotBlank(ticket) || wasGatewayed) { filterChain.doFilter(request, response);
return
; }
final
String modifiedServiceUrl; log.debug("no ticket and no assertion found");
if
(
this
.gateway) { log.debug("setting gateway attribute in session"); modifiedServiceUrl =
this
.gatewayStorage.storeGatewayInformation(request, serviceUrl); }
else
{ modifiedServiceUrl = serviceUrl; }
if
(log.isDebugEnabled()) { log.debug("Constructed service url: " + modifiedServiceUrl); }
// 如果用户没有登录过,那么构造重定向的URL
final
String urlToRedirectTo = CommonUtils.constructRedirectUrl(
this
.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl,
this
.renew,
this
.gateway);
if
(log.isDebugEnabled()) { log.debug("redirecting to \"" + urlToRedirectTo + "\""); }
// 重定向跳转到Cas认证中心
response.sendRedirect(urlToRedirectTo); }
大概意思,就是如果确认没有登录(url中没有ticket,并且 session也没有或者session里没有验证实体)就会被重定向到
https://login.xn.com/login?service=http://www.xn.com/1.html
2. 浏览器收到重定向的response,就会访问
https://login.xn.com/login?service=http://www.xn.com/1.html
, CAS SERVER会 从 login.xn.com域的 cookie 检查是否有 CASTGC,并获取一下service里的URL,会发现没有 CASTGC,所以,是个没登录的用户,就会出现登录页,用户会输入并验证.
如果验证通过, 会创建TGT(就是TGC对应在 CAS SERVER 上的实体,这里,应该会有TGT<->Service的对应),并且把 TGC写入到 Cookie(login.xn.com域下),并且因为有 service的存在,(这里service主要是用来告诉CAS SERVER,你得告诉浏览器,一会儿重定向到源URL上才行),所以会生成 ST (service ticket),并告诉浏览器,重定向到 www.xn.com/1.html?ticket=st123456上.
3. 浏览器会根据重定向的地址 www.xn.com/1.html?ticket=st123456上请求WEB服务器,所以,会继续进入
AuthenticationFilter ,但是这次因为有了ticket,根据源码,会进入下一个filter
Cas20ProxyReceivingTicketValidationFilter
, 该拦截器用来与
CAS Server 进行身份核实,以确保 Service Ticket 的合法性.
// 构造验证URL,向cas server发起验证请求
final
Assertion assertion =
this
.
ticketValidator.validate(ticket, constructServiceUrl(request, response));
if
(log.isDebugEnabled()) { log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName()); }
// 如果验证成功,设置assertion,当再一次发起访问请求时,如果发现assertion已经被设置,所以已经通过验证,不过再次重定向会cas认证中心
request.setAttribute(CONST_CAS_ASSERTION, assertion);
if
(
this
.useSession) { request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion); }
这里会发现,如果从CAS SERVER上验证ST没问题,就应该是登录成功了,会设置 assertion到Session中. 这里写了,会构造URL,感觉应该类似于 http client针对 cas server发起了一次请求,然后返回 assertion.
// 生成验证URL,如果你debug会发现,此处会构造一个类似以下的URL,访问的是cas server的serviceValidate方法 ,示例如下 //
https://demo.testcas.com/cas/
serviceValidate
?
ticket=
ST-31-cioaDNxSpUWIgeYEn4yK-cas
&
service=
http%3A%2F%2Fapp1.testcas.com%2Fb2c-haohai-server%2Fuser%2FcasLogin
在 CAS SEVER那里, 会触发
final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);
这个函数, 所以,是感觉server与 ST的对应关系,来检查ST是否有效
如果发现没问题了,就会通知浏览器,重定向 到 www.xn.com/1.html上,就可以了,这是最后一次重定向(这里的assertion感觉应该也会保存用户的一些信息,不然不可能每次client getuser的时候,都要链接CAS SERVER).
3. 当浏览器收到重定向通知的时候, 就开始访问 源URL www.xn.com/1.html, 同样会触发 filter,这是,第一个filter会检查,发现已经登录了,然后交给ticketfilter, 发现没有 ticket,继续沿着 chain往下(剩下的应该不重要,会继续触发其他的HTTP行为了),这就是第一登录的完整过程了.
当用户继续新标签打开www.xn.net的其他链接,都会被authenticationfilter拦截, session存在,并且有 assertion,所以,会是正常的.
如果这个时候,关闭浏览器 jsession会失效,所以,再打开浏览器的时候,访问又有变化了
因为session里没有 assertion了(jsession如果变了,肯定找不到用户上次回话对应的session了),所以这次访问,authentication会检查到session失效了,于是,又开始重定向到 https://login.xn.com/login?service=***上,但是 因为 login.xn.com的cookie的存在(里面有TGC),所以CAS SERVER会根据TGC知道,这个用户登录过,于是就可以不用登录了,直接生成 ST,然后告诉浏览器重定向到 service对应的RUL+?ticket=st333333,继续上面的验证了.
(这里有一点,登录成功后 ,jsession id 跟 service 的对应关系,也会保留在CAS SERVER的,这个应该是注销的时候,会用到.)
当用户访问 cs.xn.com/productCenter/index.html的时候,因为配置的CAS CLIENT,所以,会依然被重定向到 https://login.xn.com/login?service=cs.xn.com/productCenter/index.html ,这时,跟上面类同,也会先查TGC,发现登录过,直接返回 st,然后继续校验走一遍,就可以通过 assertion获取 user了,无须登录.
这里项目出现过一个问题,
项目首页是个静态页面,里面有ajax异步请求JAVA的后台服务,所以,如果session莫名失效, ajax请求的时候,会被 filter重定向,因为跨域的关系,请求肯定会失败的.
如果只是单单解决这个问题,可以随便在ajax执行前,有其他没有被排除的静态资源访问,先触发filter的话,应该可以避免ajax跨域重定向的问题.(比如,可以加载一个 不存在的img(这样不会被缓存,这个404的img会直接请求WEB服务的,或者在第一个