內部邀請碼: C8E245J (不寫邀請碼,沒有現金送)
國內私募機構九鼎控股打造,九鼎投資是在全國股份轉讓系統掛牌的公眾公司,股票代碼為430719,為“中國PE第一股”,市值超1000億元。
在CAS中MVC的控制主要是使用的spring MVC來實現的。但是,在登錄過程中,因為有點類似於工作流的性質,所以,采用了一個輕量級的工作流框架,就是spring 的weflow。下面,我們就CAS如何采用webflow控制登錄流程進行分析。
想深入理解webflow工作原理的讀者需要參考官方的webflow2.21版本的reference。
好了,話不多說,開始CAS認證流程之旅。
用戶請求如何進入認證流程的
用戶一開始輸入http://localhost:8080/CASServer 的時候,默認是回去訪問 index.jsp的。我們看一下index.jsp,里面的內容非常簡單。
final String queryString = request.getQueryString(); final String url = request.getContextPath() +"/login" + (queryString!= null ?"?" + queryString : ""); response.sendRedirect(response.encodeURL(url));
這里進行的處理就是將用戶的請求重定向到CAS認證中心的 /login 路徑下。在上一章中,我們已經知道,該路徑我們是由 名為 CAS 的servlet,也就是org.jasig.cas.web.init.SafeDispatcherServlet 進行處理的。我們進入該servlet看一下他是如何進行處理的。
進入該servlet中,我們看到,他繼承自 HttpServlet,也就是說他只是一個普通的servlet。該servlet持有一個DispatcherServlet 屬性delegate。這同struts的處理方式如出一轍。都是通過一個代理類來進行處理請求的。
該類的初始化也僅僅是調用代理類delegate的初始化。Service函數很僅僅是調用delegate的service函數。
看來,DispatcherServlet類才是處理請求的關鍵類。我們看到DispatcherServlet類的完整路徑是
org.springframework.web.servlet.DispatcherServlet
這里,也就是將請求交給了spring MVC處理了。(關於spring mvc,參考我其他的關於spring 的源碼分析文章)。
Webflow與Spring MVC集成
Spring MVC核心配置文件是cas-servlet.xml。在該文件中,webflow將與springMVC進行集成。
這里有一個問題,就是spring何時開始加載cas-servlet.xml文件的呢?
原來,在初始化DispatcherServlet的時候,會自動加載 servlet-name+“-servlet.xml”文件。所以,cas-servlet.xml是自動加載的,不需要在配置文件進行配置。(參見關於springMVC的文章)
交給spring MVC之后,spring MVC又將請求交給了 webflow處理。下面是webflow同spring MVC的結合:
<!-- 根據工作流定義,生成一個執行器 --> <webflow:flow-executorid="flowExecutor"flow-registry="flowRegistry"> <webflow:flow-execution-attributes> <webflow:always-redirect-on-pausevalue="false"/> </webflow:flow-execution-attributes> </webflow:flow-executor> <!-- 注冊一個工作流 id是子路徑 為flow入口對login的請求交由login-webflow.xml定義的處理器進行處理--> <webflow:flow-registryid="flowRegistry"flow-builder-services="builder"> <webflow:flow-locationpath="/WEB-INF/login-webflow.xml"id="login"/> </webflow:flow-registry> <webflow:flow-builder-servicesid="builder"view-factory-creator="viewFactoryCreator"expression-parser="expressionParser"/>
在該文件中,我們可以看到上面的配置項。這就是將webflow框架作為spring MVC的一個節點來進行配置。
webflow:flow-registry節點就是注冊了一個webflow流程,該流程的入口,也就是ID=“login”。這樣,交給springMVC的請求路徑如果是login的,則有springMVC交給webflow處理。
在webflow中,會定義一些視圖,這些視圖都是以view=”XXX”的形式存在的。那么XXX又是如何找到對應的頁面呢??看flow-builder-services節點,我們會發現有個view-factory-creator屬性,該屬性就定義了視圖解析工廠。
該視圖解析工廠是由視圖解析器組成的。這里只定義了一個視圖解析器,就是viewResolvers。該視圖解析器是springFramework中的ResourceBundleViewResolver的一個實例,該類可以通過basenames屬性,找到value值對應的properties屬性文件,該文件中式類似ke=values類型的內容,正是該文件將jsp文件映射成視圖名稱。
至此,springMVC與webflow已經集成完畢。
Webflow配置文件及源碼分析
在WEB-INF文件夾下的login-webflow.xml是登陸流程的主要配置文件。在該文件中,定義了用戶登錄的整個處理流程。
首先,配置文件中的 on-start標簽定義了用戶第一次進入流程中的預處理動作。該標簽對應spring中的id為initialFlowSetupAction的bean。查看該bean(InitialFlowSetupAction)的代碼。該類需要繼承自AbstractAction,AbstractAction方法是org.springframework.webflow.action包中的類。是webflow中的基礎類。該類中的doExecute方法是對應處理業務的方法。就猶如servlet中的service方法一樣。該方法的參數是RequestContext對象,該參數是一個流程的容器。該方法從request中獲取TGT,並且構建一個臨時的service對象(不同域注冊的service,詳情見接入系統管理)。並且,將TGT和service放在FlowScope作用域中。
流程的初始化完畢之后,就開始一系列的判斷了。也就是進入decision-state節點。這些節點是依次執行的。
<!--檢查flow中是否存在TGT如果存在,存在進入hasServiceCheck,為空進入gatewayRequestCheck--> <decision-stateid="ticketGrantingTicketExistsCheck"> <if test="flowScope.ticketGrantingTicketIdneq null"then="hasServiceCheck"else="gatewayRequestCheck"/> </decision-state> <!-- 主要是CS結構使用gatewat,暫時不研究--> <decision-stateid="gatewayRequestCheck"> <if test="externalContext.requestParameterMap['gateway']neq '' && externalContext.requestParameterMap['gateway'] neqnull && flowScope.service neq null" then="gatewayServicesManagementCheck"else="viewLoginForm"/> </decision-state> <!-- 存在TGT,說明用戶已經登陸,測試flow中service是否為空,不為空,進入renewRequestCheck,為空,進入viewGenericLoginSuccess--> <decision-stateid="hasServiceCheck"> <if test="flowScope.service!= null"then="renewRequestCheck"else="viewGenericLoginSuccess"/> </decision-state> <!-- 用戶已經登陸,且請求參數中存在service判斷請求中是否存在'renew'參數,如果renew參數為空或者沒有內容,那么,進入viewLoginForm,否則進入generateServiceTicket renew參數和gateway參數不兼容。renew參數將繞過單點登錄。也就是說即使用戶登錄了,還將要求用戶登錄。(等你妹啊,人家都登錄了,憑什么還要讓人家再登錄一次) --> <decision-stateid="renewRequestCheck"> <if test="externalContext.requestParameterMap['renew']neq '' && externalContext.requestParameterMap['renew'] neqnull"then="viewLoginForm"else="generateServiceTicket"/> </decision-state> <decision-stateid="warn"> <if test="flowScope.warnCookieValue"then="showWarningView"else="redirect"/> </decision-state>
對應的dicision-state走完之后,如果不存在TGT,其實就會進入voiwLoginForm節點。該節點是一個view-state類型的,這也就是說明該節點是一個頁面,view=“casLoginView”屬性定義了該view對應的頁面是“casLoginView”。這個視圖會被spring的視圖解析器解析成/WEB-INF/view/jsp/default/ui/casLoginView.jsp頁面。用戶這時候就能看到一個登陸界面了。
需要注意的是,用戶看到的登錄界面中,會有hidden類型的一個lt參數:
<inputtype="hidden" name="lt"value="${flowExecutionKey}" />
該參數可以理解成每個需要登錄的用戶都有一個流水號。只有有了webflow發放的有效的流水號,用戶才可以說明是已經進入了webflow流程。否則,沒有流水號的情況下,webflow會認為用戶還沒有進入webflow流程,從而會重新進入一次webflow流程,從而會重新出現登錄界面。
用戶點擊登錄之后,提交到realSubmit節點。該節點執行的是authenticationViaFormAction.submit方法,在該方法中,將會驗證用戶的認證信息是否正確。驗證成功之后,跳轉到sendTicketGrantingTicket,在這里,將生成TGT,然后,進入serviceCheck,在serviceCheck中,將會驗證flowScope作用域中是否存在service,如果存在,則進入generateServiceTicket,否則進入登錄成功頁面。在generateServiceTicket中,也將生成ST,同時檢查是否有警告信息,然后進行重定向到用戶最開始請求的地址。
至此,springMVC與webflow整合以及登錄整個流程已經講解完畢。
這里講解的比較粗略。后續可能會較詳細的寫一下webflow的工作原理。另外,如果對於自定義登錄流程如何處理,將會在特殊場景的解決方案中給出。