亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

CAS 5.2.x 單點(diǎn)登錄 - 實(shí)現(xiàn)原理及源碼淺析

elisa.yang / 1710人閱讀

摘要:上一篇文章簡(jiǎn)單介紹了在本地開發(fā)環(huán)境中搭建服務(wù)端和客戶端,對(duì)單點(diǎn)登錄過程有了一個(gè)直觀的認(rèn)識(shí)之后,本篇將探討單點(diǎn)登錄的實(shí)現(xiàn)原理。因此引入服務(wù)端作為用戶信息鑒別和傳遞中介,達(dá)到單點(diǎn)登錄的效果。為該流程的實(shí)現(xiàn)類。表示對(duì)返回結(jié)果的處理。

上一篇文章簡(jiǎn)單介紹了 CAS 5.2.2 在本地開發(fā)環(huán)境中搭建服務(wù)端和客戶端,對(duì)單點(diǎn)登錄過程有了一個(gè)直觀的認(rèn)識(shí)之后,本篇將探討 CAS 單點(diǎn)登錄的實(shí)現(xiàn)原理。

一、Session 和 Cookie

HTTP 是無狀態(tài)協(xié)議,客戶端與服務(wù)端之間的每一次通訊都是獨(dú)立的,而會(huì)話機(jī)制可以讓服務(wù)端鑒別每次通訊過程中的客戶端是否是同一個(gè),從而保證業(yè)務(wù)的關(guān)聯(lián)性。Session 是服務(wù)器使用一種類似于散列表的結(jié)構(gòu),用來保存用戶會(huì)話所需要的信息。Cookie 作為瀏覽器緩存,存儲(chǔ) Session ID 以到達(dá)會(huì)話跟蹤的目的。

由于 Cookie 的跨域策略限制,Cookie 攜帶的會(huì)話標(biāo)識(shí)無法在域名不同的服務(wù)端之間共享。
因此引入 CAS 服務(wù)端作為用戶信息鑒別和傳遞中介,達(dá)到單點(diǎn)登錄的效果。

二、CAS 流程圖

官方流程圖,地址:https://apereo.github.io/cas/...

瀏覽器與 APP01 服務(wù)端

瀏覽器第一次訪問受保護(hù)的 APP01 服務(wù)端,由于未經(jīng)授權(quán)而被攔截并重定向到 CAS 服務(wù)端。

瀏覽器第一次與 CAS 服務(wù)端通訊,鑒權(quán)成功后由 CAS 服務(wù)端創(chuàng)建全局會(huì)話 SSO Session,生成全局會(huì)話標(biāo)識(shí) TGT 并存儲(chǔ)在瀏覽器 Cookie 中。

瀏覽器重定向到 APP01,重寫 URL 地址帶上全局會(huì)話標(biāo)識(shí) TGT。

APP01 拿到全局會(huì)話標(biāo)識(shí) TGT 后向 CAS 服務(wù)端請(qǐng)求校驗(yàn),若校驗(yàn)成功,則 APP01 會(huì)獲取到已經(jīng)登錄的用戶信息。

APP01 創(chuàng)建局部會(huì)話 Session,并將 SessionID 存儲(chǔ)到瀏覽器 Cookie 中。

瀏覽器與 APP01 建立會(huì)話。

瀏覽器與 APP02 服務(wù)端

瀏覽器第一次訪問受保護(hù)的 APP02 服務(wù)端,由于未經(jīng)授權(quán)而被攔截并重定向到 CAS 服務(wù)端。

瀏覽器第二次與 CAS 服務(wù)端通訊,CAS 校驗(yàn) Cookie 中的全局會(huì)話標(biāo)識(shí) TGT。

瀏覽器重定向到 APP02,重寫 URL 地址帶上全局會(huì)話標(biāo)識(shí) TGT。

APP02 拿到全局會(huì)話標(biāo)識(shí) TGT 后向 CAS 服務(wù)端請(qǐng)求校驗(yàn),若校驗(yàn)成功,則 APP02 會(huì)獲取到已經(jīng)登錄的用戶信息。

APP02 創(chuàng)建局部會(huì)話 Session,并將 SessionID 存儲(chǔ)到瀏覽器 Cookie 中。

瀏覽器與 APP02 建立會(huì)話。

三、相關(guān)源碼 3.1 CAS客戶端 3.1.1 根據(jù)是否已登錄進(jìn)行攔截跳轉(zhuǎn)

以客戶端攔截器作為入口,對(duì)于用戶請(qǐng)求,如果是已經(jīng)校驗(yàn)通過的,直接放行:
org.jasig.cas.client.authentication.AuthenticationFilter#doFilter

// 不進(jìn)行攔截的請(qǐng)求地址
if (isRequestUrlExcluded(request)) {
    logger.debug("Request is ignored.");
    filterChain.doFilter(request, response);
    return;
}

// Session已經(jīng)登錄
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;
}

// 從請(qǐng)求中獲取ticket
final String serviceUrl = constructServiceUrl(request, response);
final String ticket = retrieveTicketFromRequest(request);
final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
    filterChain.doFilter(request, response);
    return;
}

否則進(jìn)行重定向:
org.jasig.cas.client.authentication.AuthenticationFilter#doFilter

this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);

對(duì)于Ajax請(qǐng)求和非Ajax請(qǐng)求的重定向,進(jìn)行分別處理:
org.jasig.cas.client.authentication.FacesCompatibleAuthenticationRedirectStrategy#redirect

public void redirect(final HttpServletRequest request, final HttpServletResponse response,
        final String potentialRedirectUrl) throws IOException {

    if (CommonUtils.isNotBlank(request.getParameter(FACES_PARTIAL_AJAX_PARAMETER))) {
        // this is an ajax request - redirect ajaxly
        response.setContentType("text/xml");
        response.setStatus(200);

        final PrintWriter writer = response.getWriter();
        writer.write("");
        writer.write(String.format("",
                potentialRedirectUrl));
    } else {
        response.sendRedirect(potentialRedirectUrl);
    }
}
3.1.2 校驗(yàn)Ticket

如果請(qǐng)求中帶有 Ticket,則進(jìn)行校驗(yàn),校驗(yàn)成功返回用戶信息:
org.jasig.cas.client.validation.AbstractTicketValidationFilter#doFilter

final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));
logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
request.setAttribute(CONST_CAS_ASSERTION, assertion);

打斷點(diǎn)得知返回的信息為 XML 格式字符串:
org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator#validate

logger.debug("Retrieving response from server.");
final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);

XML 文件內(nèi)容示例:


    
        casuser
        
            UsernamePasswordCredential
            true
            2018-03-25T22:09:49.768+08:00[GMT+08:00]
            AcceptUsersAuthenticationHandler
            AcceptUsersAuthenticationHandler
            false
            
    

最后將 XML 字符串轉(zhuǎn)換為對(duì)象 org.jasig.cas.client.validation.Assertion,并存儲(chǔ)在 Session 或 Request 中。

3.1.3 重寫Request請(qǐng)求

定義過濾器:
org.jasig.cas.client.util.HttpServletRequestWrapperFilter#doFilter

其中定義 CasHttpServletRequestWrapper,重寫 HttpServletRequestWrapperFilter:

final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private final AttributePrincipal principal;

        CasHttpServletRequestWrapper(final HttpServletRequest request, final AttributePrincipal principal) {
            super(request);
            this.principal = principal;
        }

        public Principal getUserPrincipal() {
            return this.principal;
        }

        public String getRemoteUser() {
            return principal != null ? this.principal.getName() : null;
        }
        // 省略其他代碼

這樣使用以下代碼即可獲取已登錄用戶信息。

AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
3.2 CAS服務(wù)端 3.2.1 用戶密碼校驗(yàn)

服務(wù)端采用了 Spirng Web Flow,以 login-webflow.xml 為入口:


    
    
    
    
    
    

action-state代表一個(gè)流程,其中 id 為該流程的標(biāo)識(shí)。
evaluate expression為該流程的實(shí)現(xiàn)類。
transition表示對(duì)返回結(jié)果的處理。

定位到該流程對(duì)應(yīng)的實(shí)現(xiàn)類authenticationViaFormAction,可知在項(xiàng)目啟動(dòng)時(shí)實(shí)例化了對(duì)象AbstractAuthenticationAction

@ConditionalOnMissingBean(name = "authenticationViaFormAction")
@Bean
@RefreshScope
public Action authenticationViaFormAction() {
    return new InitialAuthenticationAction(initialAuthenticationAttemptWebflowEventResolver,
            serviceTicketRequestWebflowEventResolver,
            adaptiveAuthenticationPolicy);
}

在頁面上點(diǎn)擊登錄按鈕,進(jìn)入:
org.apereo.cas.web.flow.actions.AbstractAuthenticationAction#doExecute
org.apereo.cas.authentication.PolicyBasedAuthenticationManager#authenticate

經(jīng)過層層過濾,得到執(zhí)行校驗(yàn)的AcceptUsersAuthenticationHandler和待校驗(yàn)的UsernamePasswordCredential

執(zhí)行校驗(yàn),進(jìn)入
org.apereo.cas.authentication.AcceptUsersAuthenticationHandler#authenticateUsernamePasswordInternal

@Override
protected HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential,
                                                             final String originalPassword) throws GeneralSecurityException {
    if (this.users == null || this.users.isEmpty()) {
        throw new FailedLoginException("No user can be accepted because none is defined");
    }
    // 頁面輸入的用戶名
    final String username = credential.getUsername();
    // 根據(jù)用戶名取得緩存中的密碼
    final String cachedPassword = this.users.get(username);

    if (cachedPassword == null) {
        LOGGER.debug("[{}] was not found in the map.", username);
        throw new AccountNotFoundException(username + " not found in backing map.");
    }
    // 校驗(yàn)緩存中的密碼和用戶輸入的密碼是否一致
    if (!StringUtils.equals(credential.getPassword(), cachedPassword)) {
        throw new FailedLoginException();
    }
    final List list = new ArrayList<>();
    return createHandlerResult(credential, this.principalFactory.createPrincipal(username), list);
}
3.2.2 登錄頁Ticket校驗(yàn)

在 login-webflow.xml 中定義了 Ticket 校驗(yàn)流程:


    
    
    
    

org.apereo.cas.web.flow.TicketGrantingTicketCheckAction#doExecute

@Override
protected Event doExecute(final RequestContext requestContext) {
    // 從請(qǐng)求中獲取TicketID
    final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext);
    if (!StringUtils.hasText(tgtId)) {
        return new Event(this, NOT_EXISTS);
    }

    String eventId = INVALID;
    try {
        // 根據(jù)TicketID獲取Tciket對(duì)象,校驗(yàn)是否失效
        final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);
        if (ticket != null && !ticket.isExpired()) {
            eventId = VALID;
        }
    } catch (final AbstractTicketException e) {
        LOGGER.trace("Could not retrieve ticket id [{}] from registry.", e.getMessage());
    }
    return new Event(this, eventId);
}

可知 Ticket 存儲(chǔ)在服務(wù)端的一個(gè) Map 集合中:
org.apereo.cas.AbstractCentralAuthenticationService#getTicket(java.lang.String, java.lang.Class)

3.2.3 客戶端Ticket校驗(yàn)

對(duì)于從 CAS 客戶端發(fā)送過來的 Ticket 校驗(yàn)請(qǐng)求,則會(huì)進(jìn)入服務(wù)端以下代碼:
org.apereo.cas.DefaultCentralAuthenticationService#validateServiceTicket

從 Ticket 倉庫中,根據(jù) TicketID 獲取 Ticket 對(duì)象:

final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

在同步塊中校驗(yàn) Ticket 是否失效,以及是否來自合法的客戶端:

synchronized (serviceTicket) {
    if (serviceTicket.isExpired()) {
        LOGGER.info("ServiceTicket [{}] has expired.", serviceTicketId);
        throw new InvalidTicketException(serviceTicketId);
    }

    if (!serviceTicket.isValidFor(service)) {
        LOGGER.error("Service ticket [{}] with service [{}] does not match supplied service [{}]",
                serviceTicketId, serviceTicket.getService().getId(), service);
        throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService());
    }
}

根據(jù) Ticket 獲取已登錄用戶:

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

最后將用戶信息返回給客戶端。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/71024.html

相關(guān)文章

  • cas工作原理淺析與總結(jié)

    摘要:是大學(xué)發(fā)起的一個(gè)企業(yè)級(jí)的開源的項(xiàng)目,旨在為應(yīng)用系統(tǒng)提供一種可靠的單點(diǎn)登錄解決方法屬于。實(shí)現(xiàn)原理是先通過的認(rèn)證,然后向申請(qǐng)一個(gè)針對(duì)于的,之后在訪問時(shí)把申請(qǐng)到的針對(duì)于的以參數(shù)傳遞過去。后面的流程與上述流程步驟及以后步驟類似 CAS( Central Authentication Service )是 Yale 大學(xué)發(fā)起的一個(gè)企業(yè)級(jí)的、開源的項(xiàng)目,旨在為 Web 應(yīng)用系統(tǒng)提供一種可靠的單點(diǎn)登...

    warkiz 評(píng)論0 收藏0
  • CAS 5.2.x 單點(diǎn)登錄 - 搭建服務(wù)端和客戶端

    摘要:一簡(jiǎn)介單點(diǎn)登錄,簡(jiǎn)稱為,是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一??蛻舳藬r截未認(rèn)證的用戶請(qǐng)求,并重定向至服務(wù)端,由服務(wù)端對(duì)用戶身份進(jìn)行統(tǒng)一認(rèn)證。三搭建客戶端在官方文檔中提供了客戶端樣例,即。 一、簡(jiǎn)介 單點(diǎn)登錄(Single Sign On),簡(jiǎn)稱為 SSO,是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系...

    Lin_YT 評(píng)論0 收藏0
  • Python Flask單點(diǎn)登錄問題

    摘要:如果一旦加密算法泄露了,攻擊者可以在本地建立一個(gè)實(shí)現(xiàn)了登錄接口的假冒父應(yīng)用,通過綁定來把子應(yīng)用發(fā)起的請(qǐng)求指向本地的假冒父應(yīng)用,并作出回應(yīng)。 1.什么是單點(diǎn)登錄? 單點(diǎn)登錄(Single Sign On),簡(jiǎn)稱為 SSO,是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)??蛻舳顺钟蠭D,服務(wù)端持有session...

    tuomao 評(píng)論0 收藏0
  • 號(hào)外:友戶通支持企業(yè)自有用戶中心啦

    摘要:針對(duì)這種情況,友戶通特定開發(fā)了聯(lián)邦用戶中心來支持企業(yè)的自有用戶中心。友戶通支持通過協(xié)議使用企業(yè)內(nèi)部的支持協(xié)議的用戶中心賬號(hào)進(jìn)行登錄。友戶通目前支持標(biāo)準(zhǔn)協(xié)議以及友戶通自定義協(xié)議可供企業(yè)集成。 友戶通做用友云的用戶系統(tǒng)也一年多了,經(jīng)常聽實(shí)施、售前等說要私有化部署友戶通,原因無非是企業(yè)的考慮到用戶安全性和單一用戶賬號(hào)的需求。但由于用戶管理的復(fù)雜性,友戶通部署與維護(hù)并不容易,因此經(jīng)常糾結(jié)在用戶...

    妤鋒シ 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<