1、[置顶] CAS单点登录源码解析之【客户端】

前期准备:

2. cas-client-3.2.1-release.zip
3.应用系统webapp(http://127.0.0.1:8090/webapp/main.do)
4.CAS单点登录服务器端(http://127.0.0.1:8081/cas-server/)
        本次讨论包括CAS单点登录客户端的部分源码,以及在此基础上进行二次开发,因此需要修改部分CAS客户端的源码,源码部分的修改在下面进行讨论。关于 CAS服务器端的源码分析,请参考另一篇文章 http://blog.csdn.net/dovejing/article/details/44523545
其中cas-client-core-3.2.1.jar为CAS客户端的核心jar包,cas-client-3.2.1-release.zip为CAS客户端的源码zip包。
web.xml部分代码
[html]  view plain copy
  1. <filter>  
  2.     <filter-name>CASFilter</filter-name>  
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4. </filter>  
  5.   
  6. <filter>  
  7.     <filter-name>CASValidationFilter</filter-name>  
  8.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  9. </filter>  
  10.   
  11. <filter>  
  12.     <filter-name>initLoginUserFilter</filter-name>  
  13.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  14. </filter>  
  15.   
  16. <filter-mapping>  
  17.     <filter-name>CASFilter</filter-name>  
  18.     <url-pattern>/*</url-pattern>  
  19. </filter-mapping>  
  20.   
  21. <filter-mapping>  
  22.     <filter-name>CASValidationFilter</filter-name>  
  23.     <url-pattern>/*</url-pattern>  
  24. </filter-mapping>  
  25.   
  26. <filter-mapping>  
  27.     <filter-name>initLoginUserFilter</filter-name>  
  28.     <url-pattern>/*</url-pattern>  
  29. </filter-mapping>  

spring-context.xml部分代码

[html]  view plain copy
  1. <bean id="CASFilter" class="com.master.client.filter.AuthenticationFilter" init-method="initConfig"/>  
  2. <bean id="CASValidationFilter" class="com.master.client.filter.TicketValidationFilter" init-method="initConfig"/>  
  3. <bean id="initLoginUserFilter" class="com.master.client.filter.InitLoginUserFilter" init-method="initConfig"/>  

cas_config.properties代码

[html]  view plain copy
  1. service = http://127.0.0.1:8090/webapp/main.do  
  2. casServerUrlPrefix = http://127.0.0.1:8081/cas-server/  
  3. casServerLoginUrl = http://127.0.0.1:8081/cas-server/login  
  4. casServerLogoutUrl = http://127.0.0.1:8081/cas-server/logout  
  5. errorUrl = http://127.0.0.1:8081/cas-server/error  

        首先,从web.xml配置信息可以看出,有三个过滤器负责处理单点登录流程,其中前两个为流程必须处理的过滤器,最后一个是我新增加的过滤器,主要功能是初始化用户信息和权限。既然是源码解读,我们必然要看源码。下面就从CAS客户端的源码给大家进行解读CAS单点登录客户端的工作流程。之后会单独写一篇关于CAS单点登录服务器的源码解读的文章。

AuthenticationFilter

        从单点登录第一个过滤器开始解读,源码中的第一个过滤器是AuthenticationFilter,因为业务需求对此过滤器进行了二次开发。源码是从web.xml配置文件中读取初始化参数,我对此做了修改,从properties文件中读取初始化参数。把单点登录的配置参数定义到properties文件中,这样做的好处是对初始化配置参数统一管理,一目了然。源码走起。

AuthenticationFilter的父类AbstractCasFilter的初始化方法,主要工作是从properties中获取单点登录的系统参数。使大家看的更清楚,源码中省略了日志的输出。

[java]  view plain copy
  1. public void initConfig() throws ServletException {  
  2.     Properties conf = PropertiesUtil.getConfigProperties();  
  3.     setServerName(conf.getProperty("serverName"null));  
  4.     setService(conf.getProperty("service"null));  
  5.     setArtifactParameterName(conf.getProperty("artifactParameterName""ticket"));  
  6.     setServiceParameterName(conf.getProperty("serviceParameterName""service"));  
  7.     setEncodeServiceUrl(parseBoolean(conf.getProperty("encodeServiceUrl""true")));  
  8.     //登录错误跳转到该url地址  
  9.     setErrorUrl(conf.getProperty("errorUrl"null));  
  10.           
  11.     //初始化配置文件(AuthenticationFilter类)  
  12.     initInternalConfig(conf);  
  13.     init();//初始化  
  14. }  

AuthenticationFilter的初始化方法。主要工作是从properties中获取单点登录的系统参数。使大家看的更清楚,源码中省略了日志的输出。

[java]  view plain copy
  1. public void initInternalConfig(Properties conf) throws ServletException {  
  2.     if (!isIgnoreInitConfiguration()) {  
  3.         setCasServerLoginUrl(conf.getProperty("casServerLoginUrl"null));  
  4.         setRenew(parseBoolean(conf.getProperty("renew""false")));  
  5.         setGateway(parseBoolean(conf.getProperty("gateway""false")));  
  6.   
  7.         String gatewayStorageClass = conf.getProperty("gatewayStorageClass"null);  
  8.   
  9.         if (gatewayStorageClass == null)  
  10.             return;  
  11.         try {  
  12.             this.gatewayStorage = ((GatewayResolver) Class.forName(gatewayStorageClass).newInstance());  
  13.         } catch (Exception e) {  
  14.             this.log.error(e, e);  
  15.             throw new ServletException(e);  
  16.         }  
  17.     }  
  18. }  

AuthenticationFilter的过滤方法。

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

        当我们从浏览器访问配置了单点登录的应用系统时(http://127.0.0.1:8090/webapp/main.do),由于集成了CAS单点登录客户端,此时进入到第一个过滤器AuthenticationFilter(不考虑其他非单点登录的过滤器),执行以下操作:

  1. 从session中获取名为“_const_cas_assertion_”的assertion对象,判断assertion是否存在,如果存在,说明已经登录,执行下一个过滤器。如果不存在,执行第2步。
  2. 生成serviceUrl(http://127.0.0.1:8090/webapp/main.do),从request中获取票据参数ticket,判断ticket是否为空,如果不为空执行下一个过滤器。如果为空,执行第3步。
  3. 生成重定向URL(http://127.0.0.1:8081/cas-server/login?service=http://127.0.0.1:8090/webapp/main.do)。
  4. 跳转到单点登录服务器,显示登录页面,此时第一个过滤器执行完成。

TicketValidationFilter

AbstractTicketValidationFilter的过滤方法。

[java]  view plain copy
  1. public final void doFilter(final ServletRequest servletRequest,  
  2.         final ServletResponse servletResponse, final FilterChain filterChain)  
  3.         throws IOException, ServletException {  
  4.     if (!preFilter(servletRequest, servletResponse, filterChain)) {  
  5.         return;  
  6.     }  
  7.   
  8.     final HttpServletRequest request = (HttpServletRequest) servletRequest;  
  9.     final HttpServletResponse response = (HttpServletResponse) servletResponse;  
  10.     //从request中获取参数  
  11.     final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());  
  12.     //ticket不为空,验证ticket,否则本过滤器处理完成,处理下个过滤器  
  13.     if (CommonUtils.isNotBlank(ticket)) {  
  14.         if (this.log.isDebugEnabled()) {  
  15.             this.log.debug("Attempting to validate ticket: " + ticket);  
  16.         }  
  17.         try {  
  18.             //验证ticket并产生Assertion对象,错误抛出TicketValidationException异常  
  19.             final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));  
  20.   
  21.             if (this.log.isDebugEnabled()) {  
  22.                 this.log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());  
  23.             }  
  24.             //request设置assertion  
  25.             request.setAttribute(CONST_CAS_ASSERTION, assertion);  
  26.             //session设置assertion  
  27.             if (this.useSession) {  
  28.                 request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);  
  29.             }  
  30.             onSuccessfulValidation(request, response, assertion);  
  31.   
  32.             if (this.redirectAfterValidation) {  
  33.                 this.log.debug("Redirecting after successful ticket validation.");  
  34.                 response.sendRedirect(constructServiceUrl(request, response));  
  35.                 return;  
  36.             }  
  37.         } catch (final TicketValidationException e) {  
  38.             response.setStatus(403);  
  39.             this.log.info("获得用户认证信息失败:" + e.getMessage());  
  40.             //跳转到错误页面  
  41.             response.sendRedirect(getErrorUrl() + "?errortype=" + e.getMessage() + "&" + createService());  
  42.             return;  
  43.         }  
  44.     }  
  45.     //本过滤器处理完成,处理下个过滤器  
  46.     filterChain.doFilter(request, response);  
  47. }  

        假设当执行完第一个过滤器后,跳转到CAS服务器端的登录页面,输入用户名和密码,验证通过后。CAS服务器端会生成ticket,并将ticket作为重新跳转到应用系统的参数(http://127.0.0.1:8090/webapp/main.do?ticket=ST-1-4hH2s5tzsMGCcToDvGCb-cas01.example.org)。此时又进入第一个过滤器AuthenticationFilter,由于存在ticket参数,进入到第二个过滤器TicketValidationFilter,执行以下操作:

  1. 从request获取ticket参数,如果ticket为空,继续处理下一个过滤器。如果参数不为空,验证ticket参数的合法性。
  2. 验证ticket,TicketValidationFilter的validate方法通过httpClient访问CAS服务器端(http://127.0.0.1:8081/cas-server/serviceValidate?ticket=ST-1-4hH2s5tzsMGCcToDvGCb-cas01.example.org&service=http://127.0.0.1:8090/webapp/main.do)验证ticket是否正确,并返回assertion对象。如果验证失败,抛出异常,跳转到错误页面。如果验证成功,session会以"_const_cas_assertion_"的名称保存assertion对象,继续处理下一个过滤器。

InitLoginUserFilter

InitLoginUserFilter的过滤方法。
[java]  view plain copy
  1. public final void doFilter(final ServletRequest servletRequest,  
  2.         final ServletResponse servletResponse, final FilterChain filterChain)  
  3.         throws IOException, ServletException {  
  4.       
  5.     try {  
  6.         //加上自己应用的逻辑,例如构造用户的信息和权限等  
  7.         //...  
  8.           
  9.         //本过滤器处理完成,处理下个过滤器  
  10.         filterChain.doFilter(request, response);  
  11.         return;  
  12.     } catch (Exception e) {  
  13.         //跳转到错误页面  
  14.         res.sendRedirect(getErrorUrl() + "?errortype=" + e.getMessage() + "&" + createService());  
  15.     }  
  16. }  

        这个过滤器是我新增加的过滤器,以满足应用系统的特殊业务。当到达这个过滤器的时候,说明CAS服务器已经登录成功,此过滤器会对应用系统所需要的信息进行初始化(用户基本信息和权限等)。完成操作后,显示应用系统的首页。如果出现异常,跳转到错误页面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值