CAS Java客户端登录相关过滤器的处理流程

来源:互联网 发布:华为云计算视频百度云 编辑:程序博客网 时间:2024/06/03 18:24

首先了解一下CAS登录原理:

CAS结构中一般包含CAS服务器、应用服务器、客户端三个部分。客户端向应用服务器发出请求,由于未登录,会被跳转到CAS服务器登录。登录成功后跳转回应用服务器的登录前的URL,但是CAS服务器会给URL加上一个ticket参数。应用服务器拿着ticket去CAS服务器验证,验证成功后即加入一个session表示已登录,以后就不用再次登录了。

在web.xml配置中,AuthenticationFilter和TicketValidationFilter两个过滤器是负责处理登录流程的。

web.xml配置:

[html] view plain copy
  1. <filter>  
  2.     <filter-name>CAS Authentication Filter</filter-name>  
  3.     <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>  
  4.     <init-param>  
  5.         <param-name>casServerLoginUrl</param-name>  
  6.         <param-value>http://localhost:8080/cas/login</param-value>  
  7.     </init-param>  
  8.     <init-param>  
  9.         <param-name>serverName</param-name>  
  10.         <param-value>http://localhost:9999</param-value>  
  11.     </init-param>  
  12. </filter>  
  13. <filter-mapping>  
  14.     <filter-name>CAS Authentication Filter</filter-name>  
  15.     <url-pattern>/*</url-pattern>  
  16. </filter-mapping>  
  17. <filter>  
  18.     <filter-name>CAS Validation Filter</filter-name>  
  19.     <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>  
  20.     <init-param>  
  21.         <param-name>casServerUrlPrefix</param-name>  
  22.         <param-value>http://localhost:8080/cas</param-value>  
  23.     </init-param>  
  24.     <init-param>  
  25.         <param-name>serverName</param-name>  
  26.         <param-value>http://localhost:9999</param-value>  
  27.     </init-param>  
  28. </filter>  
  29. <filter-mapping>  
  30.     <filter-name>CAS Validation Filter</filter-name>  
  31.     <url-pattern>/*</url-pattern>  
  32. </filter-mapping>  

1、AuthenticationFilter

AuthenticationFilter中doFilter方法源码:

[java] view plain copy
  1. public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {  
  2.         final HttpServletRequest request = (HttpServletRequest) servletRequest;  
  3.         final HttpServletResponse response = (HttpServletResponse) servletResponse;  
  4.         final HttpSession session = request.getSession(false);  
  5.         final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;  
  6.   
  7.         // 如果session中的CONST_CAS_ASSERTION(即"_const_cas_assertion_")不为空,则跳过此过滤器  
  8.         if (assertion != null) {  
  9.             filterChain.doFilter(request, response);  
  10.             return;  
  11.         }  
  12.   
  13.         final String serviceUrl = constructServiceUrl(request, response);  
  14.         final String ticket = CommonUtils.safeGetParameter(request,getArtifactParameterName());  
  15.         final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);  
  16.   
  17.         // 如果ticket参数不为空,则跳过此过滤器  
  18.         if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {  
  19.             filterChain.doFilter(request, response);  
  20.             return;  
  21.         }  
  22.   
  23.         final String modifiedServiceUrl;  
  24.   
  25.         log.debug("no ticket and no assertion found");  
  26.         if (this.gateway) {  
  27.             log.debug("setting gateway attribute in session");  
  28.             modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);  
  29.         } else {  
  30.             modifiedServiceUrl = serviceUrl;  
  31.         }  
  32.   
  33.         if (log.isDebugEnabled()) {  
  34.             log.debug("Constructed service url: " + modifiedServiceUrl);  
  35.         }  
  36.   
  37.         final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);  
  38.   
  39.         if (log.isDebugEnabled()) {  
  40.             log.debug("redirecting to \"" + urlToRedirectTo + "\"");  
  41.         }  
  42.   
  43.         // 否则跳转到CAS Server登录页面  
  44.         response.sendRedirect(urlToRedirectTo);  
  45.     }  

通过源码可以看出这个过滤器的处理流程:

如果session中包含name为"_const_cas_assertion_"的属性,也就是用户已经登录过了,则跳过此过滤器。

如果ticket参数不为空,即可能是登录后跳转回来的URL,则跳过此过滤器。(注意,AuthenticationFilter只判断ticket是否为空,并不做ticket合法校验,也就是随便输入一个ticket参数在URL中都可以通过此过滤器。而负责校验ticket的是第二个过滤器:TicketValidationFilter 。)

如果上面两个条件都不满足,也就是既没有"_const_cas_assertion_"的session又没有ticket参数,则跳转到XML配置的casServerLoginUrl,让用户到CAS Server上登录,并在URL加上一个参数service(即XML配置的serverName加上相对路径,用于登录成功后返回登录前的页面)。

下面来测试一下,web.xml先只配置一个过滤器,去掉其他过滤器:

在浏览器中输入地址http://localhost:9999/a/b/c并打开,会跳到登录页面,也就是XML中配置的casServerLoginUrl,并加上一个用于返回登录前页面的参数,这个参数由XML配置的serverName加上路径/a/b/c生成,即http://localhost:8080/cas/login?service=http://localhost:9999/a/b/c。再次页面输入用户名、密码登录,如果登录成功,则跳转到service参数指定的页面,并加上一个参数ticket,即http://localhost:9999/a/b/c?ticket=ST-12-1XGQRUtFnwtqQxdNLOdv-cas01.example.org。

这里可以再测试一下AuthenticationFilter是否校验ticket合法性。例如在浏览器中打开http://localhost:9999/a/b/c?ticket=123,其中ticket参数是随便写的,肯定不是合法的,但是访问可以直接进入页面,不再需要登录。也就是AuthenticationFilter只判断ticket是否为空,并不校验是否合法。

2、TicketValidationFilter

由于TicketValidationFilter、Cas20ProxyReceivingTicketValidationFilter都继承自AbstractTicketValidationFilter,下面看AbstractTicketValidationFilter中的doFilter方法源码:

[java] view plain copy
  1. public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {  
  2.   
  3.     if (!preFilter(servletRequest, servletResponse, filterChain)) {  
  4.         return;  
  5.     }  
  6.   
  7.     final HttpServletRequest request = (HttpServletRequest) servletRequest;  
  8.     final HttpServletResponse response = (HttpServletResponse) servletResponse;  
  9.     final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());  
  10.   
  11.     // 如果ticket不为空,则校验此ticket,否则直接跳过此过滤器  
  12.     if (CommonUtils.isNotBlank(ticket)) {  
  13.         if (log.isDebugEnabled()) {  
  14.             log.debug("Attempting to validate ticket: " + ticket);  
  15.         }  
  16.   
  17.         try {  
  18.             // 校验ticket,校验失败抛出异常  
  19.             final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));  
  20.   
  21.             if (log.isDebugEnabled()) {  
  22.                 log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());  
  23.             }  
  24.   
  25.             request.setAttribute(CONST_CAS_ASSERTION, assertion);  
  26.   
  27.             if (this.useSession) {  
  28.                 // 设置session中的CONST_CAS_ASSERTION(即"_const_cas_assertion_")属性  
  29.                 request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);  
  30.             }  
  31.             onSuccessfulValidation(request, response, assertion);  
  32.   
  33.             if (this.redirectAfterValidation) {  
  34.                 log. debug("Redirecting after successful ticket validation.");  
  35.   
  36.                 // URL去掉ticket参数并跳转  
  37.                 response.sendRedirect(constructServiceUrl(request, response));  
  38.                 return;  
  39.             }  
  40.         } catch (final TicketValidationException e) {  
  41.             response.setStatus(HttpServletResponse.SC_FORBIDDEN);  
  42.             log.warn(e, e);  
  43.   
  44.             onFailedValidation(request, response);  
  45.   
  46.             if (this.exceptionOnValidationFailure) {  
  47.                 throw new ServletException(e);  
  48.             }  
  49.   
  50.             return;  
  51.         }  
  52.     }  
  53.   
  54.     filterChain.doFilter(request, response);  
  55.   
  56. }  

通过源码可以看出这个过滤器的处理流程:

如果有ticket参数,校验ticket是否合法(不合法则异常:org.jasig.cas.client.validation.TicketValidationException: CAS Server could not validate ticket)。

如果合法则在session加入"_const_cas_assertion_",并再次跳转,这次跳转主要就是去掉ticket参数,即从http://localhost:9999/a/b/c?ticket=ST-12-1XGQRUtFnwtqQxdNLOdv-cas01.example.org跳转到http://localhost:9999/a/b/c。(这样做有个好处就是如果用户F5刷新页面,由于已经没有ticket参数,不会再次去校验ticket,而同一个ticket只能使用一次,再次去CAS服务器校验会出现TicketValidationException异常。)

如果没有ticket参数,则直接跳过此过滤器(没有ticket参数的话就一定是session中包含"_const_cas_assertion_"的属性,否则连第一个过滤器AuthenticationFilter都无法通过)。

整理一下整个登录流程:

第一次请求应用服务器:

当用户第一次访问应用服务器的URL,由于session中没有"_const_cas_assertion_"且参数中没有ticket,会被AuthenticationFilter跳转到CAS服务器的登录页面。

第二次请求应用服务器:

在CAS服务器的登录页面成功登录以后,会跳转到应用服务器登录前的页面,但是加上了一个参数ticket。此次请求由于有ticket参数,通过了AuthenticationFilter,但是TicketValidationFilter会对ticket进行校验,校验成功后,会在session中加入"_const_cas_assertion_",再去掉ticket参数进行一次跳转。

第三次请求应用服务器:

此时由于session中已经有了"_const_cas_assertion_",会通过AuthenticationFilter,由于没有ticket参数,也通过了TicketValidationFilter,也就是可以正常显示出这个页面了。以后再请求应用服务器就和这次一样了,由于session包含"_const_cas_assertion_"即可正常访问。


作者:叉叉哥   转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/38082761

原创粉丝点击