1. 程式人生 > >CAS原始碼追蹤系列三:cas-server端對請求的處理

CAS原始碼追蹤系列三:cas-server端對請求的處理

目錄

系列:

上一篇,我們瞭解了AuthenticationFilter對請求的過濾,如果發現session中沒有名為_const_cas_assertion_的assertion物件,而且request中也沒有對應的ticket,那麼就會跳轉到統一登入頁面。
那這次我們就來看看cas-server如何處理統一登入(版本:cas-server-3.5.2)。

InitialFlowSetupAction

首先會進入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;
    }
//將TGT放在FlowScope作用域中
    context.getFlowScope().put(
"ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
//將warnCookieValue放在FlowScope作用域中
    context.getFlowScope().put(
"warnCookieValue", Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));
//獲取service引數
final Service service = WebUtils.getService(this.argumentExtractors, context);
if (service != null && logger.isDebugEnabled()) {
        logger.debug("Placing service in FlowScope: " + service.getId());
    }
//將service放在FlowScope作用域中
    context.getFlowScope().put("service", service);
return result("success");
}

上面主要做的就是把ticketGrantingTicketId,warnCookieValue和service放到FlowScope的作用域中。

接下來需要做校驗:TicketGrantingTicket是都否存在。
如果不存在(首次登入肯定不存在)就會到下一個校驗:request中是否有gateway引數(即是都接入閘道器),如果沒有則進入下一個校驗:服務認證檢查。

ServiceAuthorizationCheck

進入ServiceAuthorizationCheck,他做的操作如下:

protected Event doExecute(final RequestContext context)throws Exception {
final Service service = WebUtils.getService(context);
//No service == plain /login request. Return success indicating transition to the login form
if(service == null) {
return success();
    }
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
if (registeredService == null) {
        logger.warn("Unauthorized Service Access for Service: [ {} ] - service is not defined in the service registry.", service.getId());
thrownew UnauthorizedServiceException();
    }
elseif (!registeredService.isEnabled()) {
        logger.warn("Unauthorized Service Access for Service: [ {} ] - service is not enabled in the service registry.", service.getId());
thrownew UnauthorizedServiceException();
    }
return success();
}

主要做的就是判斷FlowScope作用域中是否存在請求指定的service,如果service存在,查詢service的註冊資訊,看看是都存在和是否被禁用,如果不存在或者禁用了則會丟擲未認證服務異常。

然後生成LT為字首的登入票據loginTicket並將其放到flowScope中。

再然後就是進入登入頁面。該頁面中有三個隱藏引數:loginTicket、execution、_eventId

AuthenticationViaFormAction

當輸入使用者名稱和密碼,點選登入按鈕時,會執行AuthenticationViaFormAction的doBind方法進行身份繫結。

publicfinalvoiddoBind(final RequestContext context, final Credentials credentials)throws Exception {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
//bean中沒有注入,這裡什麼也不做
if (this.credentialsBinder != null && this.credentialsBinder.supports(credentials.getClass())) {
this.credentialsBinder.bind(request, credentials);
    }
}

然後就會執行sumbit方法進行真正的表單提交。

publicfinal 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);
//判斷FlowScope和request中的loginTicket是否相同
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";
    }
//FlowScope中獲取TGT
final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
//FlowScope中獲取service
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 (isCauseAuthenticationException(e)) {
                populateErrorsInstance(e, messageContext);
return getAuthenticationExceptionEventId(e);
            }
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
if (logger.isDebugEnabled()) {
                logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials", e);
            }
        }
    }
try {
//根據使用者憑證構造TGT,把TGT放到requestScope中,同時把TGT快取到伺服器的cache中
        WebUtils.putTicketGrantingTicketInRequestScope(context, 
this.centralAuthenticationService.createTicketGrantingTicket(credentials));
        putWarnCookieIfRequestParameterPresent(context);
return"success";
    } catch (final TicketException e) {
        populateErrorsInstance(e, messageContext);
if (isCauseAuthenticationException(e))
return getAuthenticationExceptionEventId(e);
return"error";
    }
}

主要做的就是判斷FlowScope和request中的loginTicket是否相同。如果不同跳轉到錯誤頁面,如果相同,則根據使用者憑證生成TGT(登入成功票據),並放到requestScope作用域中,同時把TGT快取到伺服器的cache中。

SendTicketGrantingTicketAction

根據TGT生成cookie並將其新增到response返回給客戶端。

GenerateServiceTicketAction

然後進行service檢查,判斷FlowScope中是否存在service,如果存在(類似於http://127.0.0.1:8081/cas-server/login?service=http://127.0.0.1:8090/webapp1/main.do)就去呼叫doExecute方法生成服務票據ServiceTicket

protected Event doExecute(final RequestContext context){
//獲取service
final Service service = WebUtils.getService(context);
//獲取TGT
final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
try {
//根據TGT和service生成service ticket(ST-2-97kwhcdrBW97ynpBbZH5-cas01.example.org)
final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicket,
            service);
//ST放到requestScope中
        WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
return success();
    } catch (final TicketException e) {
if (isGatewayPresent(context)) {
return result("gateway");
        }
    }
return error();
}

要做的是獲取service和TGT,並根據service和TGT生成以ST為字首的serviceTicket(例:ST-2-97kwhcdrBW97ynpBbZH5-cas01.example.org),並把serviceTicket放到requestScope中

然後從requestScope中獲取serviceTicket,構造response物件,並把response放到requestScope中。

最後重定向到應用系統。

此時流程如下:
跳轉到應用系統(http://127.0.0.1:8090/webapp1/main.do?ticket=ST-1-4hH2s5tzsMGCcToDvGCb-cas01.example.org)。
進入CAS客戶端的AuthenticationFilter過濾器,由於session中獲取名為“const_cas_assertion”的assertion物件不存在,但是request有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/webapp1/main.do)驗證ticket是否正確,並返回assertion物件

第一次訪問接入cas的另一個應用系統

當第一次訪問接入cas的另一個應用系統時,同樣跳轉到cas-server,還是會先執行InitialFlowSetupAction的初始化,由於之前的系統已經登入過了,FlowScope中存在TGT,則檢查FlowScope中是否存在service,如果存在則判斷request中是否有renew引數,如果沒有就生成serviceTicket,後續和上文流程一樣。

總結

本文主要講了請求跳轉到cas-server之後的處理,至此對cas的單點登陸流程及原理有了更多的瞭解。

參考資料:https://blog.csdn.net/dovejing/article/details/44523545

平時的學習過程記錄一下,沒有那麼高深,希望能幫到大家,與君共同進步。我是敲程式碼的小魯班,喜歡的話給個推薦,點贊,關注吧。