Cas服務端源碼解析

此版本的源碼基於cas5.3,源碼鏈接:https://github.com/apereo/cas/tree/5.3.x

首先準備好客戶端和服務端,整個訪問的流程參見 https://apereo.github.io/cas/5.2.x/protocol/CAS-Protocol.html

整個流程的狀態變化:

服務端系統啓動—客戶端發起登陸請求—服務端校驗請求—展示登陸頁—服務端發起登陸請求—校驗登陸—生成TGT、ST令牌—重定向到客戶端—客戶端發起校驗請求—服務端校驗並返回用戶信息

下面就從以上的幾個大步驟簡單解析下,本文着重關注服務端的流程,客戶端的流程可以參見: https://my.oschina.net/woniuyi/blog/4454460

 

1. 系統啓動

這個階段主要關注一些重要的配置類加載以及登陸流程的定義。

  • CasSupportActionsConfiguration

 啓動的時候會初始化系列action,這些action就是真正在運行時候處理請求的,比較典型的action如下:action名稱-實際類型

authenticationViaFormAction-InitialAuthenticationAction   認證請求

serviceAuthorizationCheck-ServiceAuthorizationCheck        客戶端校驗

sendTicketGrantingTicketAction-SendTicketGrantingTicketAction   發送tgt

createTicketGrantingTicketAction-CreateTicketGrantingTicketAction  創建tgt

 

  • DefaultLoginWebflowConfigurer

 此類做了一些初始化操作:初始化流,異常,視圖狀態等

protected void doInitialize() {
    final Flow flow = getLoginFlow();
    if (flow != null) {
        createInitialFlowActions(flow);
        createDefaultGlobalExceptionHandlers(flow);
        createDefaultEndStates(flow);
        createDefaultDecisionStates(flow);
        createDefaultActionStates(flow);
        createDefaultViewStates(flow);
        createRememberMeAuthnWebflowConfig(flow);


        setStartState(flow, CasWebflowConstants.STATE_ID_INITIAL_AUTHN_REQUEST_VALIDATION_CHECK);
    }

 

  • 登錄流程定義

cas登錄流程的完整定義在:web-flow.xml中

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.springframework.org/schema/webflow"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow.xsd">


    <action-state id="initializeLoginForm">
        <evaluate expression="initializeLoginAction" />
        <transition on="success" to="viewLoginForm"/>
    </action-state>


    <view-state id="viewLoginForm" view="casLoginView" model="credential">
        <binder>
            <binding property="username" required="true"/>
            <binding property="password" required="true"/>
        </binder>
        <transition on="submit" bind="true" validate="true" to="realSubmit" history="invalidate"/>
    </view-state>


    <action-state id="realSubmit">
        <evaluate expression="authenticationViaFormAction"/>
        <transition on="warn" to="warn"/>
        <transition on="success" to="createTicketGrantingTicket"/>
        <transition on="successWithWarnings" to="showAuthenticationWarningMessages"/>
        <transition on="authenticationFailure" to="handleAuthenticationFailure"/>
        <transition on="error" to="initializeLoginForm"/>
    </action-state>


</fl

大致意思就是:

action-state id="initializeLoginForm">

當接收到請求的時候就會初始化登錄表單,由initializeLoginAction處理,如果成功的話,就進入viewLoginForm視圖狀態。

<view-state id="viewLoginForm" view="casLoginView" model="credential">

他綁定了一個credential對象,username,password必填,在頁面點擊提交的時候,觸發realSubmit動作。

 <action-state id="realSubmit">

realSubmit狀態由authenticationViaFormAction處理,成功的話,就觸發createTicketGrantingTicket 狀態。如果處理錯誤就回到initializeLoginForm初始化登錄請求。

cas5.3版本中,以上流程較爲簡單,很多細節過程都沒有體現出來,在跟蹤代碼的時候,實際有很多處理邏輯,而在更早版本,web-flow.xml 每個流轉過程都是寫的十分詳細的,大家可以參看:https://my.oschina.net/indestiny/blog/202454,此文對整個流程定義做了詳細的闡述,可以幫助大家理解整個登錄流程。

 

2. 訪問應用

 當我們登錄cas服務端或者通過第三方應用跳轉到cas 服務器時:http://127.0.0.1:8444/cas/login,會經過一系列webflow 流程。典型流程如下:

  1. 初始化訪問

  • InitialFlowSetupAction

客戶端的請求進來首先會經過InitialFlowSetupAction,doExecute要做的就是把ticketGrantingTicketId,warnCookieValue和service放到FlowScope的作用域中,以便在登錄流程中的state中進行判斷。

public Event doExecute(final RequestContext context) {
    final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
    if (request.getMethod().equalsIgnoreCase(HttpMethod.POST.name())) {
        WebUtils.putInitialHttpRequestPostParameters(context);
    }
    //設置 TGC cookie路徑 /cas 或者/
    configureCookieGenerators(context);
    //cookie,靜態認證,密碼策略等放入flowScop,TGT放入FlowScope/RequestScope
    configureWebflowContext(context);
    //將service進行註冊並且放入RequestScope
    configureWebflowContextForService(context);
    return success();
}

 

  • InitialAuthenticationRequestValidationAction

初始化認證請求校驗

啥沒幹,就返回一個success的事件,暫時不清楚有什麼具體作用。

protected Event doExecute(final RequestContext requestContext) {
    return this.rankedAuthenticationProviderWebflowEventResolver.resolveSingle(requestContext);
}

 

  • TicketGrantingTicketCheckAction

校驗request 上下文的TGT是否合法。

當我們第一次訪問集成了CAS單點登錄的應用系統,此時應用系統會跳轉到CAS單點登錄的服務器端。此時,request的cookies中不存在CASTGC(TGT),因此FlowScope作用域中的ticketGrantingTicketId爲null

public Event doExecute(final RequestContext requestContext) {
    // 第一次tgt爲null 返回tgt不存在的事件
    final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext);
    if (StringUtils.isBlank(tgtId)) {
        return new Event(this, CasWebflowConstants.TRANSITION_ID_TGT_NOT_EXISTS);
    }
     // 否則就校驗tgt的合法性返回相應的事件
        final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);
        if (ticket != null && !ticket.isExpired()) {
            return new Event(this, CasWebflowConstants.TRANSITION_ID_TGT_VALID);
        }


    return new Event(this, CasWebflowConstants.TRANSITION_ID_TGT_INVALID);
}

 

  • ServiceAuthorizationCheck

判斷FlowScope作用域中是否存在service,不存在返回成功事件,如果service存在,查找service的註冊信息,判斷service是否符合註冊服務訪問要求,不合法則將未授權的serice放入FlowScope

protected Event doExecute(final RequestContext context) {
    final Service service = authenticationRequestServiceSelectionStrategies.resolveService(serviceInContext);
    if (service == null) {
        return success();
    }   
    final RegisteredService registeredService = this.servicesManager.findServiceBy(service);   
    if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) { 
        WebUtils.putUnauthorizedRedirectUrlIntoFlowScope(context,                registeredService.getAccessStrategy().getUnauthorizedRedirectUrl());
        throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
    }


    return success();
}

 

  • InitializeLoginAction

初始化登錄行爲,直接返回成功事件,進入登錄頁面

protected Event doExecute(final RequestContext requestContext) throws Exception {
    LOGGER.debug("Initialized login sequence");
    return success();
}

 經過上面一系列的初始化判定,由於第一次訪問,沒有tgt信息,就會顯示出登錄頁面。輸入用戶密碼就會進入下一個階段。

2. 表單提交

  • InitialAuthenticationAction-AbstractAuthenticationAction

此類是處理登陸請求的核心所在,調用鏈真的有點長。整體的步驟就是下面的三個邏輯,複雜的登陸請求代理給initialAuthenticationAttemptWebflowEventResolver處理。

protected Event doExecute(final RequestContext requestContext) {
  // 第一次 ticket請求事件處理, 爲null
    final Event serviceTicketEvent = this.serviceTicketRequestWebflowEventResolver.resolveSingle(requestContext);
    if (serviceTicketEvent != null) {
        fireEventHooks(serviceTicketEvent, requestContext);
        return serviceTicketEvent;
    }
// 此處是調用認證,得到認證的最後結果時間success
    final Event finalEvent = this.initialAuthenticationAttemptWebflowEventResolver.resolveSingle(requestContext);
    // 觸發認證完成的事件
    fireEventHooks(finalEvent, requestContext);
    return finalEvent;
}

-------初始認證事件處理器initialAuthenticationAttemptWebflowEventResolver 的resolveSingle幹了啥---------

認證調用鏈如下圖所示:

DefaultAuthenticationSystemSupport:handleInitialAuthenticationTransaction()

DefaultAuthenticationTransactionManager:handle()

PolicyBasedAuthenticationManager:authenticate()

AbstractUsernamePasswordAuthenticationHandler

ChainingPrincipalResolver

 

以上的委託流程看不懂沒關係,我們挑重要的說:InitialAuthenticationAttemptWebflowEventResolver 這個類主要完成了以下三個事情

  • 由PolicyBasedAuthenticationManager處理具體的認證邏輯,返回AuthenticationResultBuilder
  • 認證後的事件處理
  • 認證結果放入conversationScope
  1. 獲取AuthenticationResultBuilder

由 InitialAuthenticationAttemptWebflowEventResolver resolveInternal方法處理,最後得到AuthenticationResultBuilder

public Set<Event> resolveInternal(final RequestContext context) {
    try {
    // 從上下文獲取憑證及service
        final Credential credential = getCredentialFromContext(context);
        final Service service = WebUtils.getService(context);
        if (credential != null) {
            final AuthenticationResultBuilder builder = this.authenticationSystemSupport.handleInitialAuthenticationTransaction(service, credential);
           

先看下提交的數據context有些啥?

credential就是提交的用戶和密碼,service則是客戶端的的信息

接下來由PolicyBasedAuthenticationManager 實現類的authenticate方法進行處理:

  • PolicyBasedAuthenticationManager

public Authentication authenticate(final AuthenticationTransaction transaction) throws AuthenticationException {
// 調用認證預處理器
    final boolean result = invokeAuthenticationPreProcessors(transaction);    
// 將憑證放入當前線程    
AuthenticationCredentialsThreadLocalBinder.bindCurrent(transaction.getCredentials());
// 執行內部認證
    final AuthenticationBuilder builder = authenticateInternal(transaction);
}

authenticateInternal方法內部認證

這個是核心認證的邏輯

找到認證處理器和用戶解析器,進行認證和解析

// 獲取憑證
final Collection<Credential> credentials = transaction.getCredentials();
//將憑證放入AuthenticationBuilder
final AuthenticationBuilder builder = new DefaultAuthenticationBuilder(NullPrincipal.getInstance());
credentials.forEach(cred -> builder.addCredential(new BasicCredentialMetaData(cred)));
獲取當前事務的認證處理器
final Set<AuthenticationHandler> handlerSet = getAuthenticationHandlersForThisTransaction(transaction);

認證處理器有2個,org.apereo.cas.authentication.AcceptUsersAuthenticationHandler做真正的處理。

下一步,迭代所有的憑證,從handler中找到能夠處理的認證處理器,如果能夠處理,則進行認證及解析用戶信息(Principal)

// 根據handler找到用戶解析器
final PrincipalResolver resolver = getPrincipalResolverLinkedToHandlerIfAny(handler, transaction);  
// 認證並解析用戶      
authenticateAndResolvePrincipal(builder, credential, resolver, handler);

更多的認證內部實現參見:AbstractUsernamePasswordAuthenticationHandlerdoAuthentication()

更多的用戶解析實現參見:ChainingPrincipalResolver:resolve()

認證結果和用戶信息都會放在AuthenticationBuilder認證建造器中,返回builder,內部認證完成下一步對結果進行進一步封裝

 

  • 信息封裝

填充認證方法屬性,認證元數據屬性,調用認證後處理,認證結果放入線程中

    final Authentication authentication = builder.build();
    addAuthenticationMethodAttribute(builder, authentication);
    populateAuthenticationMetadataAttributes(builder, transaction);
    invokeAuthenticationPostProcessors(builder, transaction);


    final Authentication auth = builder.build();
    final Principal principal = auth.getPrincipal();
    
        principal.getId(), principal.getAttributes(), transaction.getCredentials());
        //最後將認證結果放入線程中
    AuthenticationCredentialsThreadLocalBinder.bindCurrent(auth);


    return auth

我們看下最後的認證的結果的builder有些啥:用戶信息,憑證,認證原數據,認證成功的處理器

 以上是完成了認證的請求處理

2. 認證調用結束,事件處理

認證完成返回AuthenticationResultBuilder,進行認證後的事件處理,當前流程中並沒有需要處理的事件

final Set<Event> resolvedEvents = resolveCandidateAuthenticationEvents(context, service, 
registeredService);

3. 放入緩存

授予TGT給認證結果,其實是將認證結果放入conversationScope,並沒有生成TGT,名字取得有迷惑性

grantTicketGrantingTicketToAuthenticationResult(context, builder, service);

以上的流程纔是真正完成了InitialAuthenticationAction的工作

  • CreateTicketGrantingTicketAction

創建或者更新TGT更新到域中

public Event doExecute(final RequestContext context) {
    final Service service = WebUtils.getService(context);
    final RegisteredService registeredService = WebUtils.getRegisteredService(context);
    // 拿到認證結果建造器
    final AuthenticationResultBuilder authenticationResultBuilder = WebUtils.getAuthenticationResultBuilder(context);
    
    //先從請求域或者flow中獲取tgt->null
    final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
   // 基於ticketGrantingTicket生成新的TGT
    final TicketGrantingTicket tgt = createOrUpdateTicketGrantingTicket(authenticationResult, authentication, ticketGrantingTicket);


// 緩存TGT
    WebUtils.putTicketGrantingTicketInScopes(context, tgt);
    WebUtils.putAuthenticationResult(authenticationResult, context);
    WebUtils.putAuthentication(tgt.getAuthentication(), context);
    return success();
}
  • SendTicketGrantingTicketAction

將TGT放入cookie,以及處理TGT的銷燬,返回成功

final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);


final String ticketGrantingTicketValueFromCookie = WebUtils.getTicketGrantingTicketIdFrom(context.getFlowScope());


if (this.renewalStrategy.isParticipating(context)) {
    //將TGT放入cookie
    this.ticketGrantingTicketCookieGenerator.addCookie(context, ticketGrantingTicketId);
}


if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) {
    //  如果二者不匹配,將cookie中的tgt刪除
    this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie);
  • GenerateServiceTicketAction

完成認證,對指定的TGT和service生成唯一的st,st只是作爲客戶端的認證標識,而且只有在訪問/login的時候會生成

根據TGT獲取到ticket ,裏面包含了認證結果,獲取到認證信息

//獲取service及TGT
final Service service = WebUtils.getService(context);
final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);


try {
// 根據TGT獲取到認證信息
    final Authentication authentication = this.ticketRegistrySupport.getAuthenticationFrom(ticketGrantingTicket);


    final Service selectedService = authenticationRequestServiceSelectionStrategies.resolveService(service);
    final RegisteredService registeredService = servicesManager.findServiceBy(selectedService);
    
    WebUtils.putRegisteredService(context, registeredService);
    WebUtils.putService(context, service);


    if (registeredService != null) {
        final URI url = registeredService.getAccessStrategy().getUnauthorizedRedirectUrl();
        
// 將service中的未授權的url緩存起來
        WebUtils.putUnauthorizedRedirectUrlIntoFlowScope(context, url);
    }
    final Credential credential = WebUtils.getCredential(context);
    // 根據憑證和認證信息構建一個認證結果建造器builder
    final AuthenticationResultBuilder builder = this.authenticationSystemSupport.establishAuthenticationContextFromInitial(authentication, credential);
    final AuthenticationResult authenticationResult = builder.build(principalElectionStrategy, service);
    
// 生成st 
    final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicket, service, authenticationResult);
    
    //放入RequestScope
    WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
    return success()

生成st的邏輯詳見:DefaultCentralAuthenticationService:grantServiceTicket()

TGT和ST的關係:一對多,因爲一個tgt代表一個用戶會話,而一個用戶可以訪問多個應用,每個應用都會生成一個應用會話,即ServiceTicket

tgtId:TGT-4-ZI-xw7c3rqX-zF-vzYj1Hegm4gg3LQRip8BvKYcIoe0jT3rd-pDfTKnBKnPWFLsqcBAA013935-PC

stId:ST-4-8E5BJFvGOc57nvvTh8jebaYzOBYA013935-PC

AuthenticationResult和 Authentication的關係,前者多了credentialProvided和service屬性

 

 

  • RedirectToServiceAction

將生成的st和service拼接,重定向到客戶端

protected Event doExecute(final RequestContext requestContext) {
//http://portal.demo.qds.sd:18835/
    final WebApplicationService service = WebUtils.getService(requestContext);
    
    final Authentication auth = WebUtils.getAuthentication(requestContext);
  // ST-4-8E5BJFvGOc57nvvTh8jebaYzOBYA013935-PC
    final String serviceTicketId = WebUtils.getServiceTicketFromRequestScope(requestContext);
    
    final ResponseBuilder builder = responseBuilderLocator.locate(service);
// http://portal.demo.qds.sd:18835/?ticket=ST-4-8E5BJFvGOc57nvvTh8jebaYzOBYA013935-PC
    final Response response = builder.build(service, serviceTicketId, auth);
    return finalizeResponseEvent(requestContext, service, response);
}

 

從response中可以看到,下一步將重定向到service中,並在url後跟上st

 

4. 驗證ST

注意st有過期時間限制,斷點的時候就會過期

/cas/p3/validateService

進入controller

org.apereo.cas.web.v3.V3ServiceValidateController:handle(),返回一個視圖

@GetMapping(path = CasProtocolConstants.ENDPOINT_SERVICE_VALIDATE_V3)
protected ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
    return super.handleRequestInternal(request, response);
}

調用鏈:

下面看下主要validateServiceTicket方法的主要邏輯:

  1. 獲取service

從st獲取service和當前request請求中的service

//根據st獲取到ServiceTicket對象
final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
// 從st獲取service和當前請求中的service
final Service selectedService = resolveServiceFromAuthenticationRequest(serviceTicket.getService());
final Service resolvedService = resolveServiceFromAuthenticationRequest(service);



2. 校驗

判斷service是否過期,二者是否相等,校驗不過拋出異常

if (serviceTicket.isExpired())
if (!serviceTicket.isValidFor(resolvedService))

3. 獲取信息

根據st獲取到TGT,裏面包含認證信息authentication和service,認證信息包含用戶信息Principal

final TicketGrantingTicket root = serviceTicket.getTicketGrantingTicket().getRoot();
final Authentication authentication = getAuthenticationSatisfiedByPolicy(root.getAuthentication(),
    new ServiceContext(selectedService, registeredService));
final Principal principal = authentication.getPrincipal();

4. 構建Assertion

根據認證信息構建Assertion對象,最後更新st

final Assertion assertion = new DefaultAssertionBuilder(finalAuthentication)
    .with(selectedService)
    .with(serviceTicket.getTicketGrantingTicket().getChainedAuthentications())
    .with(serviceTicket.isFromNewLogin())
    .build();
    
    //最後更新st
this.ticketRegistry.updateTicket(serviceTicket);

 Assertion對象都有什麼:

看下最後客戶端收到的信息是什麼:

  

 

5. 登出

當訪問/cas/logout時候,會進入TerminateSessionAction中

  • TerminateSessionAction

@Override
public Event doExecute(final RequestContext requestContext) {
    boolean terminateSession = true;
    // isConfirmLogout = false
    if (logoutProperties.isConfirmLogout()) {
        terminateSession = isLogoutRequestConfirmed(requestContext);
    }
    if (terminateSession) {
    // 下一步去銷燬session
        return terminate(requestContext);
    }
    return this.eventFactorySupport.event(this, CasWebflowConstants.STATE_ID_WARN);
}

context中到底有些啥信息

此action的主要邏輯爲:

  • 獲取request和response;
  • 從域中獲取tgtId,結果爲null;
  • 從cookie中獲取tgtId,實際通過從request,header中獲取
  • 通知tgt所關聯的應用下線
  • 銷燬cookie,銷燬session
public Event terminate(final RequestContext context) {
    final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
    final HttpServletResponse response = WebUtils.getHttpServletResponseFromExternalWebflowContext(context);
     //null
    String tgtId = WebUtils.getTicketGrantingTicketId(context);
    if (StringUtils.isBlank(tgtId)) {
    //從cookie中獲取
        tgtId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
    }
    if (StringUtils.isNotBlank(tgtId)) {
        LOGGER.debug("Destroying SSO session linked to ticket-granting ticket [{}]", tgtId);
        
    // 通知tgt所關聯的應用下線
        final List<LogoutRequest> logoutRequests = this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId);
        WebUtils.putLogoutRequests(context, logoutRequests);
    }
    // 銷燬cookie
    LOGGER.debug("Removing CAS cookies");
    this.ticketGrantingTicketCookieGenerator.removeCookie(response);
    this.warnCookieGenerator.removeCookie(response);


    // 銷燬session
    destroyApplicationSession(request, response);
    LOGGER.debug("Terminated all CAS sessions successfully.");


    if (StringUtils.isNotBlank(logoutProperties.getRedirectUrl())) {
        WebUtils.putLogoutRedirectUrl(context, logoutProperties.getRedirectUrl());
        return this.eventFactorySupport.event(this, CasWebflowConstants.STATE_ID_REDIRECT);
    }


    return this.eventFactorySupport.success(this);

通知應用下線:

  • 獲取TicketGrantingTicket對象
  • 由登出管理者logoutManager處理登出
  • 返回登出結果
public List<LogoutRequest> destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
    try {
 // 根據ticketGrantingTicketId 獲取 TicketGrantingTicket對象
        final TicketGrantingTicket ticket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
       // 將ticket關聯的認證信息放入線程 AuthenticationCredentialsThreadLocalBinder.bindCurrent(ticket.getAuthentication());


        final List<LogoutRequest> logoutRequests = this.logoutManager.performLogout(ticket);
        // 刪除ticket
        deleteTicket(ticketGrantingTicketId);
        //事件發佈
        doPublishEvent(new CasTicketGrantingTicketDestroyedEvent(this, ticket));


        return logoutRequests;
    } catch (final InvalidTicketException e) {
        LOGGER.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);
    }
    return new ArrayList<>(0);

此處處理鏈較長:從TerminateSessionAction: doExecute()方法開始一直到DefaultSingleLogoutServiceMessageHandler:performBackChannelLogout()方法

直接看最後的處理邏輯:

通過httpClient發送請求

public boolean performBackChannelLogout(final LogoutRequest request) {
    try {
        LOGGER.debug("Creating back-channel logout request based on [{}]", request);
        final String logoutRequest = this.logoutMessageBuilder.create(request);
        final WebApplicationService logoutService = request.getService();
        logoutService.setLoggedOutAlready(true);


        LOGGER.debug("Preparing logout request for [{}] to [{}]", logoutService.getId(), request.getLogoutUrl());
        final LogoutHttpMessage msg = new LogoutHttpMessage(request.getLogoutUrl(), logoutRequest, this.asynchronous);
        LOGGER.debug("Prepared logout message to send is [{}]. Sending...", msg);
        return this.httpClient.sendMessageToEndPoint(msg);
    } catch (final Exception e) {
        LOGGER.error(e.getMessage(), e);
    }
    return false;
}

 

簡單看下發送的消息內容:post 表單請求,攜帶logoutRequest請求參數,其值爲xml格式的字符串,包含了 st

 

 

登出結果LogoutRequest

 

參考文檔:

https://my.oschina.net/indestiny/blog/202454

https://blog.csdn.net/dovejing/article/details/44523545

https://www.cnblogs.com/xuxiaojian/p/9973866.html

https://segmentfault.com/a/1190000014001205

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章