基礎模式 SSO 訪問流程主要有如下步驟:html
1. 訪問服務: SSO 客戶端發送請求訪問應用系統提供的服務資源。web
2. 定向認證: SSO 客戶端會重定向用戶請求到 SSO 服務器。spring
3. 用戶認證:用戶身份認證。express
4. 發放票據: SSO 服務器會產生一個隨機的 Service Ticket 。瀏覽器
5. 驗證票據: SSO 服務器驗證票據 Service Ticket 的合法性,驗證經過後,容許客戶端訪問服務。緩存
6. 傳輸用戶信息: SSO 服務器驗證票據經過後,傳輸用戶認證結果信息給客戶端。安全
下面是 CAS 最基本的協議過程:服務器
基礎協議圖cookie
如 上圖: CAS Client 與受保護的客戶端應用部署在一塊兒,以 Filter 方式保護 Web 應用的受保護資源,過濾從客戶端過來的每個 Web 請求,同 時, CAS Client 會分析 HTTP 請求中是否包含請求 Service Ticket( ST 上圖中的 Ticket) ,若是沒有,則說明該用戶是沒有通過認證的;因而 CAS Client 會重定向用戶請求到 CAS Server ( Step 2 ),並傳遞 Service (要訪問的目的資源地址)。 Step 3 是用戶認證過程,若是用戶提供了正確的 Credentials , CAS Server 隨機產生一個至關長度、惟1、不可僞造的 Service Ticket ,並緩存以待未來驗證,而且重定向用戶到 Service 所在地址(附帶剛纔產生的 Service Ticket ) , 併爲客戶端瀏覽器設置一個 Ticket Granted Cookie ( TGC ) ; CAS Client 在拿到 Service 和新產生的 Ticket 事後,在 Step 5 和 Step6 中與 CAS Server 進行身份覈實,以確保 Service Ticket 的合法性。網絡
在該協議中,全部與 CAS Server 的交互均採用 SSL 協議,以確保 ST 和 TGC 的安全性。協議工做過程當中會有 2 次重定向 的過程。可是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對於用戶是透明的(使用 HttpsURLConnection )。
CAS 請求認證時序圖以下:
當用戶訪問另外一個應用的服務再次被重定向到 CAS Server 的時候, CAS Server 會主動獲到這個 TGC cookie ,而後作下面的事情:
1) 若是 User 持有 TGC 且其還沒失效,那麼就走基礎協議圖的 Step4 ,達到了 SSO 的效果;
2) 若是 TGC 失效,那麼用戶仍是要從新認證 ( 走基礎協議圖的 Step3) 。
以上是在網絡上找到的相關描述,詳細請參考:
http://www.open-open.com/lib/view/open1432381488005.html
可是光看文字描述仍是不夠清晰,不如Debug來看一下。
----------------------------------------------------------------------------------
前提:
有兩個web應用
app1.testcas.com
app2.testcas.com
Cas認證中心
demo.testcas.com
第一步:訪問目標應用app1
若是想要訪問app1的網頁
例如:app1.testcas.com/user/doWelcome
這時,該請求將會被事先配置好的CAS Filter所攔截
app1的web.xml配置以下:
<filter> <filter-name>CAS Filter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://demo.testcas.com/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://app1.testcas.com</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
斷點進入該類的doFilter方法
org.jasig.cas.client.authentication.AuthenticationFilter > doFilter
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); }
第二步:請求被重定向到CAS服務器端後
根據CAS_Server端的login-webflow.xml配置
<?xml version="1.0" encoding="UTF-8"?> <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"> <var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" /> <on-start> <evaluate expression="initialFlowSetupAction" /> </on-start> <decision-state id="ticketGrantingTicketExistsCheck"> <if test="flowScope.ticketGrantingTicketId neq null" then="hasServiceCheck" else="gatewayRequestCheck" /> </decision-state> <decision-state id="gatewayRequestCheck"> <if test="externalContext.requestParameterMap['gateway'] neq '' && externalContext.requestParameterMap['gateway'] neq null && flowScope.service neq null" then="gatewayServicesManagementCheck" else="generateLoginTicket" /> </decision-state> <decision-state id="hasServiceCheck"> <if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" /> </decision-state> <decision-state id="renewRequestCheck"> <if test="externalContext.requestParameterMap['renew'] neq '' && externalContext.requestParameterMap['renew'] neq null" then="generateLoginTicket" else="generateServiceTicket" /> </decision-state> <!-- The "warn" action makes the determination of whether to redirect directly to the requested service or display the "confirmation" page to go back to the server. --> <decision-state id="warn"> <if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" /> </decision-state> <!-- <action-state id="startAuthenticate"> <action bean="x509Check" /> <transition on="success" to="sendTicketGrantingTicket" /> <transition on="warn" to="warn" /> <transition on="error" to="generateLoginTicket" /> </action-state> --> <action-state id="generateLoginTicket"> <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" /> <transition on="success" to="viewLoginForm" /> </action-state> <view-state id="viewLoginForm" view="casMyLoginView" model="credentials"> <binder> <binding property="username" /> <binding property="password" /> <binding property="imgverifycode" /> </binder> <on-entry> <set name="viewScope.commandName" value="'credentials'" /> </on-entry> <transition on="submit" bind="true" validate="true" to="imgverifycodeValidate"> <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" /> </transition> </view-state> <action-state id="imgverifycodeValidate"> <evaluate expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credentials, messageContext)" /> <transition on="error" to="generateLoginTicket" /> <transition on="success" to="realSubmit" /> </action-state> <action-state id="realSubmit"> <evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" /> <transition on="warn" to="warn" /> <transition on="success" to="sendTicketGrantingTicket" /> <transition on="error" to="generateLoginTicket" /> </action-state> <action-state id="sendTicketGrantingTicket"> <evaluate expression="sendTicketGrantingTicketAction" /> <transition to="serviceCheck" /> </action-state> <decision-state id="serviceCheck"> <if test="flowScope.service neq null" then="generateServiceTicket" else="viewGenericLoginSuccess" /> </decision-state> <action-state id="generateServiceTicket"> <evaluate expression="generateServiceTicketAction" /> <transition on="success" to="warn" /> <transition on="error" to="generateLoginTicket" /> <transition on="gateway" to="gatewayServicesManagementCheck" /> </action-state> <action-state id="gatewayServicesManagementCheck"> <evaluate expression="gatewayServicesManagementCheck" /> <transition on="success" to="redirect" /> </action-state> <action-state id="redirect"> <evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" /> <transition to="postRedirectDecision" /> </action-state> <decision-state id="postRedirectDecision"> <if test="requestScope.response.responseType.name() eq 'POST'" then="postView" else="redirectView" /> </decision-state> <!-- the "viewGenericLogin" is the end state for when a user attempts to login without coming directly from a service. They have only initialized their single-sign on session. --> <end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" /> <!-- The "showWarningView" end state is the end state for when the user has requested privacy settings (to be "warned") to be turned on. It delegates to a view defines in default_views.properties that display the "Please click here to go to the service." message. --> <end-state id="showWarningView" view="casLoginConfirmView" /> <end-state id="postView" view="postResponseView"> <on-entry> <set name="requestScope.parameters" value="requestScope.response.attributes" /> <set name="requestScope.originalUrl" value="flowScope.service.id" /> </on-entry> </end-state> <!-- The "redirect" end state allows CAS to properly end the workflow while still redirecting the user back to the service required. --> <end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" /> <end-state id="viewServiceErrorView" view="viewServiceErrorView" /> <end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" /> <global-transitions> <transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" /> <transition to="viewServiceSsoErrorView" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException" /> <transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" /> </global-transitions> </flow>
首先會進入initialFlowSetupAction
protected Event doExecute(final RequestContext context) throws Exception { final HttpServletRequest request = WebUtils.getHttpServletRequest(context); if (!this.pathPopulated) { final String contextPath = context.getExternalContext().getContextPath(); final String cookiePath = StringUtils.hasText(contextPath) ? contextPath + "/" : "/"; logger.info("Setting path for cookies to: " + cookiePath); this.warnCookieGenerator.setCookiePath(cookiePath); this.ticketGrantingTicketCookieGenerator.setCookiePath(cookiePath); this.pathPopulated = true; } // 獲取客戶端的名爲CASTGC的cookie context.getFlowScope().put( "ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)); context.getFlowScope().put( "warnCookieValue", Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request))); // 獲取要訪問的服務 final Service service = WebUtils.getService(this.argumentExtractors, context); if (service != null && logger.isDebugEnabled()) { logger.debug("Placing service in FlowScope: " + service.getId()); } context.getFlowScope().put("service", service); return result("success"); }
以後根據webflow流程,主要有兩大分歧
若是TGC而且service存在,則發放ST(service ticket)並重定向回到客戶端應用
若是首次訪問,TGC不存在,則跳轉到CAS-server的登陸頁面,以下(本登陸頁面是從新繪製,不是CAS原生登陸頁)
由於我是首次登陸,因此會跳轉到該登陸頁進行認證。
第三步:用戶認證
輸入用戶名、密碼、驗證碼,點擊登陸
這時再來看login-webflow.xml
用戶提交登陸後,按流程依次是
1.authenticationViaFormAction.doBind
<view-state id="viewLoginForm" view="casMyLoginView" model="credentials"> <binder> <binding property="username" /> <binding property="password" /> <binding property="imgverifycode" /> </binder> <on-entry> <set name="viewScope.commandName" value="'credentials'" /> </on-entry> <transition on="submit" bind="true" validate="true" to="imgverifycodeValidate"> <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" /> </transition> </view-state>
=>
2.imgverifycodeValidate(驗證碼處理爲自定義的處理,不是原生邏輯)
<action-state id="imgverifycodeValidate"> <evaluate expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credentials, messageContext)" /> <transition on="error" to="generateLoginTicket" /> <transition on="success" to="realSubmit" /> </action-state>
=>
3.realSubmit
<action-state id="realSubmit"> <evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" /> <transition on="warn" to="warn" /> <transition on="success" to="sendTicketGrantingTicket" /> <transition on="error" to="generateLoginTicket" /> </action-state>
realSubmit中執行的是authenticationViaFormAction.submit
public final String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext) throws Exception { // Validate login ticket final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context); final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context); if (!authoritativeLoginTicket.equals(providedLoginTicket)) { this.logger.warn("Invalid login ticket " + providedLoginTicket); final String code = "INVALID_TICKET"; messageContext.addMessage(new MessageBuilder().error() .code(code) .arg(providedLoginTicket) .defaultText(code) .build()); return "error"; } // 獲取TGT,首次登陸的話應該是不存在的,因此直接跳過該分歧 final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); final Service service = WebUtils.getService(context); if (StringUtils.hasText(context.getRequestParameters().get("renew")) && ticketGrantingTicketId != null && service != null) { try { final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service, credentials); WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); putWarnCookieIfRequestParameterPresent(context); return "warn"; } catch (final TicketException e) { if (e.getCause() != null && AuthenticationException.class.isAssignableFrom(e.getCause() .getClass())) { populateErrorsInstance(e, messageContext); return "error"; } this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId); if (logger.isDebugEnabled()) { logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials", e); } } } try { // 首次登陸時,用戶輸入信息驗證成功後,建立一個新的TGT WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials)); putWarnCookieIfRequestParameterPresent(context); return "success"; } catch (final TicketException e) { // 若是用戶輸入信息驗證不經過,會拋出異常,並在頁面上顯示 populateErrorsInstance(e, messageContext); return "error"; } }
=>
4.用戶信息認證經過,而且建立了新的TGT後,緩存TGT,而且生成cookie,待後續把cookie寫入客戶端
<action-state id="sendTicketGrantingTicket"> <evaluate expression="sendTicketGrantingTicketAction" /> <transition to="serviceCheck" /> </action-state>
sendTicketGrantingTicketAction.doExecute
protected Event doExecute(final RequestContext context) { final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); final String ticketGrantingTicketValueFromCookie = (String) context.getFlowScope().get("ticketGrantingTicketId"); if (ticketGrantingTicketId == null) { return success(); } // 生成Cookie而且寫入response,最終在客戶端Cookie中保存了本TGT this.ticketGrantingTicketCookieGenerator.addCookie(WebUtils.getHttpServletRequest(context), WebUtils .getHttpServletResponse(context), ticketGrantingTicketId); if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) { this.centralAuthenticationService .destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie); } return success(); }
=>
5. 而後驗證是否存在Service,若是存在,生成ST,重定向用戶到 Service 所在地址(附帶該ST ) , 併爲客戶端瀏覽器設置一個 Ticket Granted Cookie ( TGC )
serviceCheck => generateServiceTicket => warn => redirect =>postRedirectDecision
第四步:拿着新產生的ST,到 CAS Server 進行身份覈實,以確保 Service Ticket 的合法性。
當cas Service重定向到客戶端所在service時,該重定向請求一樣會被客戶端配置的過濾器所攔截,又進入了第一步處的AuthenticationFilter
可是因爲本次請求已經帶回了ST(service ticket),因此處理與首次有所不一樣。
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 (assertion != null) { filterChain.doFilter(request, response); return; } final String serviceUrl = constructServiceUrl(request, response); final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName()); final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl); // 因爲本次已經能夠取到cas service返回的新的service ticket if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
// 因此直接進入本代碼塊,而後退出 filterChain.doFilter(request, response); return; } // 不會再一次被重定向會cas 認證中心
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); }
以後,又會被web.xml中的CAS Validation Filter(Cas20ProxyReceivingTicketValidationFilter)所攔截
該攔截器用來與CAS Server 進行身份覈實,以確保 Service Ticket 的合法性
因爲 Cas20ProxyReceivingTicketValidationFilter 沒有重寫doFilter方法,因此會進入父類AbstractTicketValidationFilter的doFilter方法
AbstractTicketValidationFilter.doFilter
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { if (!preFilter(servletRequest, servletResponse, filterChain)) { return; } final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpServletResponse response = (HttpServletResponse) servletResponse; final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName()); if (CommonUtils.isNotBlank(ticket)) { if (log.isDebugEnabled()) { log.debug("Attempting to validate ticket: " + ticket); } try { // 構造驗證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); } onSuccessfulValidation(request, response, assertion); if (this.redirectAfterValidation) { log.debug("Redirecting after successful ticket validation."); response.sendRedirect(constructServiceUrl(request, response)); return; } } catch (final TicketValidationException e) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); log.warn(e, e); onFailedValidation(request, response); if (this.exceptionOnValidationFailure) { throw new ServletException(e); } return; } } filterChain.doFilter(request, response); }
this.ticketValidator.validate(..) 代碼以下
public Assertion validate(final String ticket, final String service) throws TicketValidationException { // 生成驗證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 final String validationUrl = constructValidationUrl(ticket, service); if (log.isDebugEnabled()) { log.debug("Constructing validation url: " + validationUrl); } try { log.debug("Retrieving response from server."); // 獲得cas service響應,驗證成功或者失敗 final String serverResponse = retrieveResponseFromServer(new URL( validationUrl), ticket); if (serverResponse == null) { throw new TicketValidationException( "The CAS server returned no response."); } if (log.isDebugEnabled()) { log.debug("Server response: " + serverResponse); } return parseResponseFromServer(serverResponse); } catch (final MalformedURLException e) { throw new TicketValidationException(e); } }
能夠看一下,cas server側的serverValidate的具體實現
在cas server的cas-servlet.xml中,能夠看到以下配置:
<bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/logout"> logoutController </prop> <prop key="/serviceValidate"> serviceValidateController </prop>
...
指向serviceValidateController
ServiceValidateController.handleRequestInternal(...)
protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception { final WebApplicationService service = this.argumentExtractor.extractService(request); final String serviceTicketId = service != null ? service.getArtifactId() : null; if (service == null || serviceTicketId == null) { if (logger.isDebugEnabled()) { logger.debug(String.format("Could not process request; Service: %s, Service Ticket Id: %s", service, serviceTicketId)); } return generateErrorView("INVALID_REQUEST", "INVALID_REQUEST", null); } try { final Credentials serviceCredentials = getServiceCredentialsFromRequest(request); String proxyGrantingTicketId = null; // XXX should be able to validate AND THEN use if (serviceCredentials != null) { try { proxyGrantingTicketId = this.centralAuthenticationService .delegateTicketGrantingTicket(serviceTicketId, serviceCredentials); } catch (final TicketException e) { logger.error("TicketException generating ticket for: " + serviceCredentials, e); } } final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service); final ValidationSpecification validationSpecification = this.getCommandClass(); final ServletRequestDataBinder binder = new ServletRequestDataBinder(validationSpecification, "validationSpecification"); initBinder(request, binder); binder.bind(request); if (!validationSpecification.isSatisfiedBy(assertion)) { if (logger.isDebugEnabled()) { logger.debug("ServiceTicket [" + serviceTicketId + "] does not satisfy validation specification."); } return generateErrorView("INVALID_TICKET", "INVALID_TICKET_SPEC", null); } onSuccessfulValidation(serviceTicketId, assertion); final ModelAndView success = new ModelAndView(this.successView); success.addObject(MODEL_ASSERTION, assertion); if (serviceCredentials != null && proxyGrantingTicketId != null) { final String proxyIou = this.proxyHandler.handle(serviceCredentials, proxyGrantingTicketId); success.addObject(MODEL_PROXY_GRANTING_TICKET_IOU, proxyIou); } if (logger.isDebugEnabled()) { logger.debug(String.format("Successfully validated service ticket: %s", serviceTicketId)); } return success; } catch (final TicketValidationException e) { return generateErrorView(e.getCode(), e.getCode(), new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()}); } catch (final TicketException te) { return generateErrorView(te.getCode(), te.getCode(), new Object[] {serviceTicketId}); } catch (final UnauthorizedServiceException e) { return generateErrorView(e.getMessage(), e.getMessage(), null); } }
驗證成功後,就能夠正常訪問了。