『SSOシリーズ3』CASクラスタ配備時session異常


問題
今年の第3四半期に一回溝を跳んで、今やっているプロジェクトの先端はwebを主として、このプロジェクトはSSOを使って、これも私が初めてSSOに接触しました.最近、会社がサーバをマイクロソフトクラウドに移行するついでに、コンテナに複数のインスタンスを配置し、フロントエンドで負荷バランスを取っています.移行する前に、コードにsessionが使われていないことも確認しました.複数のtomcatを配備し、負荷均衡が輪訓戦略を用いる場合.プロジェクトの中でログインをクリックして、SSOのログインインタフェースにジャンプして、SSOのログインに成功した後、私たちのサーバーに戻る時に失敗して、ブラウザの戻りはリダイレクトしすぎて、要求に失敗しました.アクセスログから見ると、複数のtomcatが順番に何度もアクセスされている.SSOに慣れていないため、2日間悩んだ後、後退して次を求める方法を選び、負荷均衡の分配アルゴリズムをIP輪訓に変更し、同じユーザーが同じtomcatにアクセスすることを保証した.これも私がSSOを理解し、勉強し始めた理由の一つです.
CASって何?
会社のプロジェクトではCASが実現するSSOを使用しており、CASはSSOを実現するのに最もよく使われるオープンソースプロジェクトである.その公式紹介はこうです
Enterprise Single Sign-On - CAS provides a friendly open source community that actively supports and contributes to the project. While the project is rooted in higher-ed open source, it has grown to an international audience spanning Fortune 500 companies and small special-purpose installations.
このブログの特徴
SSOを学ぶ過程で他のブログも見たことがありますが、SSOの概念や流れを紹介することが多く、文字の叙述が主です.この文章はcas-clientソースコードの角度からCASのどのように働くかを説明します.この文章はcasがどのように統合すべきか、どのように使用すべきかを紹介するものではありません.もしcasがまだ使用されていない場合は、まずcasがどのように統合されるかを学ぶチュートリアルを探してください.
cas-client
私がこのblogを書いたとき、cas-server 4.1に基づいていました.0、server-client 3.3.3 cas-clientを使用するには、webが必要です.xmlはブロッキングを構成します.構成後のweb.xmlは次のようになります.

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListenerlistener-class>
    listener>
    <filter>
        <filter-name>CAS Single Sign Out Filterfilter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilterfilter-class>
    filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
    <filter>
        <filter-name>CAS Authentication Filterfilter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilterfilter-class>
        <init-param>
            <param-name>casServerLoginUrlparam-name>
            <param-value>http://www.cas.com:8080/loginparam-value>
        init-param>
        <init-param>
            <param-name>serverNameparam-name>
            <param-value>http://www.casclient.com:8081param-value>
        init-param>
    filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
    <filter>
        <filter-name>CAS Validation Filterfilter-name>
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilterfilter-class>
        <init-param>
            <param-name>casServerUrlPrefixparam-name>
            <param-value>http://www.cas.com:8080param-value>
        init-param>
        <init-param>
            <param-name>serverNameparam-name>
            <param-value>http://www.casclient.com:8081param-value>
        init-param>
    filter>
    <filter-mapping>
        <filter-name>CAS Validation Filterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filterfilter-name>
        <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilterfilter-class>
    filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>
            classpath:applicationContext.xml
        param-value>
    context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
web-app>

web.xmlファイルには4つのFilterが配置されています.SingleSignOutFilter、AuthenticationFilter、Cas 20 P r o x y ReceivingTicketValidationFilter、H t t pサーブレットRequestWrapperFilterです.
SingleSignOutFilter
SingleSignOutFilterというFilterは,主に要求時に要求を終了すると,対応するUrlにリダイレクトすることを処理する.
AuthenticationFilter
AuthenticationFilterというFilterのコアコードは以下の通りです.
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;
    if (this.isRequestUrlExcluded(request)) {
        this.logger.debug("Request is ignored.");
        filterChain.doFilter(request, response);
    } else {
        HttpSession session = request.getSession(false);
        Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
        if (assertion != null) {
            filterChain.doFilter(request, response);
        } else {
            String serviceUrl = this.constructServiceUrl(request, response);
            String ticket = this.retrieveTicketFromRequest(request);
            boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
            if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
                this.logger.debug("no ticket and no assertion found");
                String modifiedServiceUrl;
                if (this.gateway) {
                    this.logger.debug("setting gateway attribute in session");
                    modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
                } else {
                    modifiedServiceUrl = serviceUrl;
                }
                this.logger.debug("Constructed service url: {}", modifiedServiceUrl);
                String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
                this.logger.debug("redirecting to \"{}\"", urlToRedirectTo);
                this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }
}
  • 1、4行目は、ログイン不要の要求であれば、処理チェーンの次のレベルに進む.
  • 2、9行目取得セッションでKeyはconst_cas_assertionのオブジェクトは、セッションを自動的に作成しない方法を使用して、このオブジェクトにassertionという名前を付けます.
  • 3、ステップ2のassertionが存在する場合、処理チェーンの次のレベルに進む.
  • 4、ステップ2のassertionが存在しない場合、14行目の取得要求パラメータはKeyがticketの値であり、このオブジェクトにticketと名付けられる.
  • 5、ステップ4のticketが存在する場合、処理チェーンの次のレベルに進む.
  • 6、ステップ4のticketが存在しない場合はcas-serverのログインインタフェースにリダイレクトします.

  • まとめてみると、このFilterの役割はログインが必要なリクエストに対して、このリクエストsessionにconst_がなければcas_assertionオブジェクトまたはパラメータにticketプロパティが含まれていない場合はcas-serverのログインインタフェースにリダイレクトします.その他の場合は、処理チェーンの次の処理に進みます.
    Cas20ProxyReceivingTicketValidationFilter
    Cas 20 P r o x y ReceivingTicketValidationFilterというFilterのコアコードは以下の通りです.
    public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (this.preFilter(servletRequest, servletResponse, filterChain)) {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            String ticket = this.retrieveTicketFromRequest(request);
            if (CommonUtils.isNotBlank(ticket)) {
                this.logger.debug("Attempting to validate ticket: {}", ticket);
                try {
                    Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response));
                    this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
                    request.setAttribute("_const_cas_assertion_", assertion);
                    if (this.useSession) {
                        request.getSession().setAttribute("_const_cas_assertion_", assertion);
                    }
                    this.onSuccessfulValidation(request, response, assertion);
                    if (this.redirectAfterValidation) {
                        this.logger.debug("Redirecting after successful ticket validation.");
                        response.sendRedirect(this.constructServiceUrl(request, response));
                        return;
                    }
                } catch (TicketValidationException var8) {
                    this.logger.debug(var8.getMessage(), var8);
                    this.onFailedValidation(request, response);
                    if (this.exceptionOnValidationFailure) {
                        throw new ServletException(var8);
                    }
                    response.sendError(403, var8.getMessage());
                    return;
                }
            }
            filterChain.doFilter(request, response);
        }
    }
  • 1,6行目にはticketが空でない場合には7行目から30行目の処理が行われ,ticketが空である場合には直接処理チェーンの次のレベルに入ることがわかる.このFilterのリクエストに入ることができ、ticketが空であると考えられるのは、登録不要と登録済み(色素4棟がconst_cas_assertionというオブジェクトを含む)の2つの場合のみであり、この2つの場合はいずれも処理チェーンの次のレベルに入ることができる.
  • 2、9行目は、ticketを使用してcas-serverに行ってticketの真偽を検証し、ユーザーの基本情報を取得し、Assertionオブジェクトにカプセル化して返すことができ、Assertionが取得したユーザー情報であることが理解できる.取得に失敗した場合、catch文が実行され、403に戻る権限がありません.
  • 3、12行目、cas-clientがセッションを使用する場合は、セッションにassertionを格納Keyをconst_cas_assertionでは、ユーザーがログインに成功したことをマークします.上のAuthenticationFilterコードでsessionで取ったのもこのオブジェクトです.
  • 4.16行目、redirectAfterValidationというパラメータがtrueに構成されている場合、クライアント302に戻り、クライアントに一度リダイレクトさせ、リダイレクトされたアドレスはこのアドレスであるかをリダイレクトさせる.このパラメータは、cas-clientがcas-serverがticketの真偽を検証したい場合にリダイレクトするかどうかを制御します.

  • まとめると、このFilterはticketが空でないことを検証する必要がある場合、cas-sererに行ってticketが有効かどうかを検証します.適切なキャッシュセッションと現在のリクエストにリダイレクトします.
    HttpServletRequestWrapperFilter
    Cas 20 P r o x y ReceivingTicketValidationFilterというFilterは、cas-clientが管理するユーザーの基本情報を簡単に取得できるようにパッケージ化されており、他の論理はありません.
    まとめ
    cas-clientはsessionを使用してユーザーIDをマークしていることがわかりますが、すべてFilterという層にカプセル化されており、ユーザー操作は必要ありません.すでに登録されている要求に対して、サーブレットに処理することができる.ログインしていないものはリダイレクトされ、servletを呼び出す方法は出ません.ユーザーはservletメソッドでrequestを呼び出す.getUserPrincipal()でユーザー情報を取得できます.
    弊社CASクラスタで発生した問題
    environment
    2つのtomcat、負荷イコライザは順番にスケジューリングし、sso-clientにuse-session=true、redirectAfterValidation=trueを構成します.
    の原因となる
  • 1、ユーザーがログインインタフェースを呼び出すと、セッションがなくticketがなく、処理されるプロセスはSingleSignOutFilter->AuthenticationFilterで、AuthenticationFilterでsso-serverログインにリダイレクトされる.
  • 2、sso-serverログインに成功した後、ビジネスサーバにリダイレクトし、ticketパラメータを携帯します.このときの処理の流れは、S i n g l e SignOutFilter->A u t h e n t icationFilter->C a s 20 P r y y y c e i n g TicketValidationFilterで、Cas 20 P r y y c e i n g TicketValidationFilterの9行目にticketを介してassertionを取得することである.redirectAfterValidation=trueのため、18行のリダイレクトが実行され、現在のリクエストにリダイレクトされます.
  • 3、負荷均衡の輪訓策略のため、要求は別のtomcatに分けられ、別のtomcatにsessionがないため(このtomcatに登録したことがなければ間違いなくない;このtomcatに登録したことがあってもなく、別のtomcatがすでにそのJSESIONIDを置き換えたため)、しかもticketを持っていないので、前の手順を繰り返し実行し、抜け出せません.

  • 解法
    今は問題が簡単ですが、sessionクラスタのことですね.セッションまたはIP負荷等化ポリシーを集中的に処理します.