`
wusuoya
  • 浏览: 631487 次
  • 性别: Icon_minigender_2
  • 来自: 成都
社区版块
存档分类
最新评论

CAS 源码分析 (非proxy模式)

    博客分类:
  • SSO
 
阅读更多

 

一、CAS 基本原理   (3,4,5,9.2,9.3是主要步骤)

第一次访问:
1. 浏览器   发起访问WebAPP 请求:  http://www.web.com/app
2. 客户端  AuthenticationFilter Filter 发现Session中无 Assertion,且URL中无 ticket 变量。生成 service url 变量,并重定向到:  https://www.cas-server.com/cas/login?service=http://www.web.com/app



3. CAS server  生成 Login ticket, service 对象,并展示 login 页面,默认提供 username / password 给用户验证。
4. CAS server 端,用户输入 username / password 验证,若通过则生成TGT,存入服务器段(默认为 Map 类型的 cache),同时将TGT id 作为 content创建 cookie 并发送到浏览器。
5. CAS server 端通过TGT 生成service ticket.  重定向到 http://www.web.com/app?ticket=ST-xxx

 

 

6. 客户端   访问 http://www.web.com/app?ticket=ST-xxx
7. 客户端   AuthenticationFilter Filter 发现URL中有 ticket, 跳过 AuthenticationFilter过滤器,到达 Cas20ProxyReceivingTicketValidationFilter过滤器。


8. 客户端 生成验证 service url:  http://www.web.com/app
9. 客户端   Cas20ProxyReceivingTicketValidationFilter 过滤器,使用6处的ticket 与8处的 service 作为参数验证。

  9.1  客户端 生成验证 servlet: https://www.cas-server.com/cas/serviceValidate?ticket=ST-xxx&service=http://www.web.com/app
  9.2  客户端 通过HttpClient访问 9.1 的 url
                注:AbstractUrlBasedTicketValidator.java line 207,如果是用CAS ptotocol验证,则第二个参数 ticket无用。
                得到如下形式的 response:

Java代码  收藏代码
  1. 546 <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>  
  2. 547         <cas:authenticationSuccess>  
  3. 548                 <cas:user>jack</cas:user>  
  4. 549   
  5. 550   
  6. 551         </cas:authenticationSuccess>  
  7. 552 </cas:serviceResponse>  

   9.3  客户端 解析 response 字符串,生成 assertion (包含 username, validate info 等)

   9.4  客户端 设置 assertion 为 request 的 _const_cas_assertion_ 属性,同时会将assertion记录到session 中去。

   9.5  客户端 如果设置了重定向属性,则重定向到 http://www.web.com/app  --  转步骤10

              否则继续执行以后的 filter,通过servlet 访问 http://www.web.com/app 服务,结束CAS的验证。

 

用户已成功登录,非第一次访问:

10. 客户端 通过重定向访问 http://www.web.com/app

11. 客户端 AuthenticationFilter Filter 发现 session 中有Assertion, 结束本过滤器,转移到下一个过滤器 Cas20ProxyReceivingTicketValidationFilter.

12. 客户段 Cas20ProxyReceivingTicketValidationFilter 发现 本次访问的URL 无 ticket,结束本次过滤,转移到下一个过滤器,继续执行以后的 filter,通过servlet 访问 http://www.web.com/app 服务。

 

二 源码解析

 

1. 客户端 web.xml  片段:

 

Xml代码  收藏代码
  1. ...  
  2.   
  3. <filter>  
  4.           <filter-name>CAS Authentication Filter</filter-name>  
  5.           <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>  
  6.           <init-param>  
  7.                 <param-name>casServerLoginUrl</param-name>  
  8.                 <param-value>https://www.colorcc.com:8443/cas/login</param-value>  
  9.           </init-param>  
  10.           <init-param>  
  11.                 <param-name>serverName</param-name>  
  12.                 <param-value>http://localhost:8080</param-value>  
  13.           </init-param>  
  14.     </filter>  
  15.     <filter>  
  16.           <filter-name>CAS Validation Filter</filter-name>  
  17.           <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>  
  18.           <init-param>  
  19.                 <param-name>casServerUrlPrefix</param-name>  
  20.                 <param-value>https://www.colorcc.com:8443/cas</param-value>  
  21.           </init-param>  
  22.           <init-param>  
  23.                 <param-name>serverName</param-name>  
  24.                 <param-value>http://localhost:8080</param-value>  
  25.           </init-param>  
  26.     <!--   <init-param>  
  27.                 <param-name>redirectAfterValidation</param-name>  
  28.                 <param-value>false</param-value>  
  29.           </init-param> -->  
  30.     </filter>  
  31.   
  32.     <filter-mapping>  
  33.         <filter-name>CAS Authentication Filter</filter-name>  
  34.         <url-pattern>/*</url-pattern>  
  35.     </filter-mapping>  
  36.     <filter-mapping>  
  37.         <filter-name>CAS Validation Filter</filter-name>  
  38.         <url-pattern>/*</url-pattern>  
  39.     </filter-mapping>  
  40. ...  

 

2.  AuthenticationFilter 代码:

Java代码  收藏代码
  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.         // 如果 session 中有 assertion,则结束 authentication 过滤器,直接跳到下一个过滤器  
  7.         if (assertion != null) {  
  8.             filterChain.doFilter(request, response);  
  9.             return;  
  10.         }  
  11.   
  12.         //  2.1 如果 session 中无 assertion, 则构造 service,  如 http://www.web.com/a1  
  13.        <strong> final String serviceUrl = constructServiceUrl(request, response);</strong>  
  14.   
  15.   
  16.   
  17.   
  18.   
  19.         final String ticket = CommonUtils.safeGetParameter(request,getArtifactParameterName());  
  20.         final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);  
  21.   
  22.         // 如果 request 中有 ticke变量,则结束本过滤器,直接跳到下一个过滤器  
  23.         if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {  
  24.             filterChain.doFilter(request, response);  
  25.             return;  
  26.         }  
  27.   
  28.         final String modifiedServiceUrl;  
  29.   
  30.         log.debug("no ticket and no assertion found");  
  31.         if (this.gateway) {  
  32.             log.debug("setting gateway attribute in session");  
  33.             modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);  
  34.         } else {  
  35.             modifiedServiceUrl = serviceUrl;  
  36.   
  37.         }  
  38.   
  39.         if (log.isDebugEnabled()) {  
  40.             log.debug("Constructed service url: " + modifiedServiceUrl);  
  41.         }  
  42.   
  43.         // 2.2 否 则构造重定向 URL, 其中 casServerLoginUrl 为 web.xml 中 filter 配 置,eg: https://www.cas-server.com/cas/login?service=http://www.web.com/a1  
  44.        final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);  
  45.   
  46.   
  47.         if (log.isDebugEnabled()) {  
  48.             log.debug("redirecting to \"" + urlToRedirectTo + "\"");  
  49.         }  
  50.   
  51.         //  2.3 重定向到 CAS server   
  52.         response.sendRedirect(urlToRedirectTo);<em>  
  53. </em>  
  54.   
  55.   
  56.   
  57.   
  58.   
  59.     }  

  2.1 构造 service url:    http://www.web.com/a1

Java代码  收藏代码
  1. protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {  
  2.         return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName, this.artifactParameterName, this.encodeServiceUrl);  
  3.     }  

 

3. 重定向URL:  https://www.cas-server.com/cas/login?service=http://www.web.com/a1, 其中 cas server的 web.xml:

Java代码  收藏代码
  1. <servlet>  
  2.     <servlet-name>cas</servlet-name>  
  3.     <servlet-class>  
  4.       org.jasig.cas.web.init.SafeDispatcherServlet  
  5.     </servlet-class>  
  6.     <init-param>  
  7.       <param-name>publishContext</param-name>  
  8.       <param-value>false</param-value>  
  9.     </init-param>  
  10.     <load-on-startup>1</load-on-startup>  
  11.   </servlet>  
  12.       
  13.   <servlet-mapping>  
  14.     <servlet-name>cas</servlet-name>  
  15.     <url-pattern>/login</url-pattern>  
  16.   </servlet-mapping>  

 

     3.1  SafeDispatcherServlet 使用 Spring DispatcherServlet 作为 delegate

 

Java代码  收藏代码
  1. public final class SafeDispatcherServlet extends HttpServlet {  
  2.   
  3.     // 定义 Spring DispatcherServlet 作为 delegate  
  4.     private DispatcherServlet delegate = new DispatcherServlet();  
  5.   
  6.     // 使用 delegate 初始化 servlet   
  7.     public void init(final ServletConfig config) {  
  8.         try {  
  9.             this.delegate.init(config);  
  10.   
  11.         } catch (final Throwable t) {  
  12.          ...  
  13.   
  14.      // 使用 delegate 的 service 执行 web 操作  
  15.      public void service(final ServletRequest req, final ServletResponse resp)  
  16.         throws ServletException, IOException {  
  17.         if (this.initSuccess) {  
  18.             this.delegate.service(req, resp);  
  19.         } else {  
  20.             throw new ApplicationContextException(  
  21.                 "Unable to initialize application context.");  
  22.         }  
  23.     }  

 

    3.2 cas-servlet.xml 配置文件如下, 可以看到 login 对应的 webflow 为: login-webflow.xml

 

Java代码  收藏代码
  1. <webflow:flow-registry id="flowRegistry" flow-builder-services="builder">  
  2.   <webflow:flow-location path="/WEB-INF/login-webflow.xml" id="login"/>  
  3. </webflow:flow-registry>  

    3.3  根据 login-webflow.xml 配置文件(结合 cas-servlet.xml):

 

Java代码  收藏代码
  1. <on-start>  
  2.         <evaluate expression="initialFlowSetupAction" />  
  3.     </on-start>  
  4.   
  5. <bean id="initialFlowSetupAction" class="org.jasig.cas.web.flow.InitialFlowSetupAction"  
  6.         p:argumentExtractors-ref="argumentExtractors"  
  7.         p:warnCookieGenerator-ref="warnCookieGenerator"  
  8.         p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"/>  

     3.4 InitialFlowSetupAction

 

Java代码  收藏代码
  1. protected Event doExecute(final RequestContext context) throws Exception {  
  2.         final HttpServletRequest request = WebUtils.getHttpServletRequest(context);  
  3.         if (!this.pathPopulated) {  
  4.             final String contextPath = context.getExternalContext().getContextPath();  
  5.             final String cookiePath = StringUtils.hasText(contextPath) ? contextPath + "/" : "/";  
  6.             logger.info("Setting path for cookies to: "  
  7.                 + cookiePath);  
  8.             this.warnCookieGenerator.setCookiePath(cookiePath);  
  9.             this.ticketGrantingTicketCookieGenerator.setCookiePath(cookiePath);  
  10.             this.pathPopulated = true;  
  11.         }  
  12.           
  13.         // 给 FlowScope 的设置 ticketGrantingTicketId, warnCookieValue 参数  
  14.         context.getFlowScope().put(  
  15.             "ticketGrantingTicketId"this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));  
  16.         context.getFlowScope().put("warnCookieValue",  
  17.             Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));  
  18.          
  19.          // 3.4.1 抽取 service 参数  
  20.         final Service service = WebUtils.getService(this.argumentExtractors, context);  
  21.   
  22.         if (service != null && logger.isDebugEnabled()) {  
  23.             logger.debug("Placing service in FlowScope: " + service.getId());  
  24.         }  
  25.   
  26.         // 给 FlowScope 的设置 service 参数  
  27.         context.getFlowScope().put("service", service);  
  28.   
  29.         return result("success");  
  30.     }  

     3.4.1  WebApplicationService.getService

Java代码  收藏代码
  1. public static WebApplicationService getService(  
  2.         final List<ArgumentExtractor> argumentExtractors,  
  3.         final HttpServletRequest request) {  
  4.         for (final ArgumentExtractor argumentExtractor : argumentExtractors) {  
  5.               
  6.             // 3.4.1.1 通过配置的 argumentExtractor 抽取 service  
  7.             final WebApplicationService service = argumentExtractor.extractService(request);  
  8.   
  9.             if (service != null) {  
  10.                 return service;  
  11.             }  
  12.         }  
  13.   
  14.         return null;  
  15.     }  

 

     3.4.1.1  CasArgumentExtractor 代码

Java代码  收藏代码
  1. public final class CasArgumentExtractor extends AbstractSingleSignOutEnabledArgumentExtractor {  
  2.   
  3.     public final WebApplicationService extractServiceInternal(final HttpServletRequest request) {  
  4.         return SimpleWebApplicationServiceImpl.createServiceFrom(request, getHttpClientIfSingleSignOutEnabled());  
  5.     }  
  6. }  
  7.   
  8. // SimpleWebApplicationServiceImpl  
  9.     private static final String CONST_PARAM_SERVICE = "service";  
  10.     private static final String CONST_PARAM_TARGET_SERVICE = "targetService";  
  11.     private static final String CONST_PARAM_TICKET = "ticket";  
  12.     private static final String CONST_PARAM_METHOD = "method";  
  13.   
  14. public static SimpleWebApplicationServiceImpl createServiceFrom(  
  15.         final HttpServletRequest request, final HttpClient httpClient) {  
  16.         final String targetService = request  
  17.             .getParameter(CONST_PARAM_TARGET_SERVICE);  
  18.         final String method = request.getParameter(CONST_PARAM_METHOD);  
  19.         final String serviceToUse = StringUtils.hasText(targetService)  
  20.             ? targetService : request.getParameter(CONST_PARAM_SERVICE);  
  21.   
  22.         if (!StringUtils.hasText(serviceToUse)) {  
  23.             return null;  
  24.         }  
  25.   
  26.         final String id = cleanupUrl(serviceToUse);  
  27.         final String artifactId = request.getParameter(CONST_PARAM_TICKET);  
  28.   
  29.         return new SimpleWebApplicationServiceImpl(id, serviceToUse,  
  30.             artifactId, "POST".equals(method) ? ResponseType.POST  
  31.                 : ResponseType.REDIRECT, httpClient);  
  32.     }  
  33.   
  34. private SimpleWebApplicationServiceImpl(final String id,  
  35.         final String originalUrl, final String artifactId,  
  36.         final ResponseType responseType, final HttpClient httpClient) {  
  37.         super(id, originalUrl, artifactId, httpClient);  
  38.         this.responseType = responseType;  
  39.     }  
  40.   
  41. protected AbstractWebApplicationService(final String id, final String originalUrl, final String artifactId, final HttpClient httpClient) {  
  42.         this.id = id;  
  43.         this.originalUrl = originalUrl;  
  44.         this.artifactId = artifactId;  
  45.         this.httpClient = httpClient;  
  46.     }  

 

 

3. Cas20ProxyReceivingTicketValidationFilter 及 AbstractTicketValidationFilter代码:

Java代码  收藏代码
  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.         // 如果 URL 中包含 ticket 参数,则执行 service 验证工作  
  12.         if (CommonUtils.isNotBlank(ticket)) {  
  13.             if (log.isDebugEnabled()) {  
  14.                 log.debug("Attempting to validate ticket: " + ticket);  
  15.             }  
  16.   
  17.             try {  
  18.                  
  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.                     request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);  
  29.                 }  
  30.                 onSuccessfulValidation(request, response, assertion);  
  31.   
  32.                 if (this.redirectAfterValidation) {  
  33.                     log. debug("Redirecting after successful ticket validation.");  
  34.                     response.sendRedirect(constructServiceUrl(request, response));  
  35.                     return;  
  36.                 }  
  37.             } catch (final TicketValidationException e) {  
  38.                 response.setStatus(HttpServletResponse.SC_FORBIDDEN);  
  39.                 log.warn(e, e);  
  40.   
  41.                 onFailedValidation(request, response);  
  42.   
  43.                 if (this.exceptionOnValidationFailure) {  
  44.                     throw new ServletException(e);  
  45.                 }  
  46.   
  47.                 return;  
  48.             }  
  49.         }  
  50.   
  51.         // 如果不包含 ticket, 直接跳过CAS Filter验证,继续其他 filter 或 web app 操作  
  52.         filterChain.doFilter(request, response);  
  53.   
  54.     }
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics