zoukankan      html  css  js  c++  java
  • CAS客户端整合(三) Otrs

    OTRS 是用Perl写的一个工单邮件系统,非常强大。

    登录流程

    流程图略过
    otrs没有像 discuz 和 zabbix 类似的游客登录状态,这样处理起来逻辑分支少一些。
    不过还是考虑用 otrs 的 session 机制,这样可以在用户已登录的时候减少想 CAS-server 认证的次数。
    因此,登录的流程基本跟Zabbix一致

    Perl-cas 客户端

    perl 没有官方的 cas 客户端代码。网上找到一个比较新的 perlcashttps://subversion.renater.fr/perlcas/trunk/
    直接用的话有点问题,需要做点修改。
    cas-server 默认使用https协议,因此需要修改 perlcas 的 curl 操作,这里是 get_https2() 方法, 改成 LWP::UserAgent

    sub get_https2 {
        my $host = shift;
        my $port = shift;
        my $path = shift;
    
        unless ( eval "require LWP::UserAgent" ) {
            $errors = sprintf
    "Unable to use LWP library, LWP::UserAgent required, install LWP (CPAN) first
    ";
            return undef;
        }
        require LWP::UserAgent;
    
        # user LWP::UserAgent
        my $ua = LWP::UserAgent->new(
            protocols_allowed => ['http', 'https'],
            timeout	          => 30,
            ssl_opt           => {
                verify_hostname => 0
            }
        );
        $ua->default_header('cookie'=>'');
        my $url = 'http://'.$host.':'.$port.$path;
        my $response = $ua->get($url);
        return $response->{_content};
    }
    

    改之前返回的是一个xml列表,改之后变成了一整个xml字符串,因此在callCAS()作以下修改:

    sub callCAS {
        my $self = shift;
        my $url  = shift;
    
        my ( $host, $port, $path ) = &_parse_url($url);
    
        my $xml = get_https2(
            $host, $port, $path,
            {
                'cafile'      => $self->{'CAFile'},
                'capath'      => $self->{'CAPath'},
                'SSL_version' => $self->{'SSL_version'}
            }
        );
    
        # use Data::Dumper; die ''.Dumper($xml);
    
        # unless ($xml && $#$xml >= 0) {
        #     warn $errors;
        #     return undef;
        # }
    
        # ## Skip HTTP header fields
        # my $line = shift @$xml;
        # while ( $line !~ /^s*$/ ) {
        #     $line = shift @$xml;
        # }
        return &_parse_xml( $xml );
    }
    

    修改登录过程

    OTRS 的用户登录验证都在 Kernel/System/Web/InterfaceAgent.pm, 根据 OTRS 开发建议,我们复制原文件到 `Custom/Kernel/System/Web/InterfaceAgent.pm',然后再进行修改。
    为了方便调用, 增加两个自定义方法:

    # logout cas.
    # 2017-11-14 by Carl
    sub _logoutCAS {
        my $Self = shift;
    
        my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
        my $ParamObject  = $Kernel::OM->Get('Kernel::System::Web::Request');
    
        my $wsCAS = new AuthCAS(
            casUrl => "http://cas.cloud.com:8088/cas-server",
        );
        my $app = 'http://'.$ENV{SERVER_NAME}.$ENV{REQUEST_URI};
        # 非常关键,
        $app =~ s/?$ENV{QUERY_STRING}//;
        ## Redirect the User for login at CAS
        ## This step is not required if we already have a PGT (Proxy Granting Ticket)
        my $login_url = $wsCAS->getServerLogoutURL($app);
        my $cookie = $ParamObject->SetCookie(
                            Key      => $ConfigObject->Get('SessionName') || 'SessionID',
                            Value    => '',
                            Expires  => '-1y',
                            Path     => $ConfigObject->Get('ScriptAlias'),
                            Secure   => 0,
                            HTTPOnly => 1,
                            );
        printf "Set-Cookie: $cookie
    Content-Type: text/html; charset=UTF-8;
    Status: 302 Found
    Location: %s
    
    ", $login_url;
        exit 0;
        return 1;
    }
    
    # login cas.
    # 2017-11-14 by Carl
    sub _loginCAS {
        my $Self = shift;
    
        my $wsCAS = new AuthCAS(
            casUrl => "http://cas.cloud.com:8088/cas-server",
        );
        my $app = 'http://'.$ENV{SERVER_NAME}.$ENV{REQUEST_URI};
        my $login_url = $wsCAS->getServerLoginURL($app);
        unless ($ENV{'QUERY_STRING'} =~ /ticket=/) {
            ## Redirect the User for login at CAS
            ## This step is not required if we already have a PGT (Proxy Granting Ticket)
            my $login_url = $wsCAS->getServerLoginURL($app);
    
            printf "Location: $login_url
    
    ";
            exit 0;
        }
    
        my $ST;
        # 非常重要,否则会导致cas-token反复认证失败
        $ENV{'QUERY_STRING'} =~ /ticket=([^&]+)/;
        $ST = $1;
        # very important!
        # Remove ST-ticket from query string
        $app =~ s/&ticket(=[^&]*)?|?ticket(=[^&]*)?&?//;
        my $User = $wsCAS->validateST($app, $ST);
        return $User;
    }
    

    首先处理登出请求

    # Handle CAS-Server logout request.
    # 2017-11-13 by Carl.
    if ( $ParamObject->GetParam(Param => 'logoutRequest') || $Param{Action} eq 'Logout' ) {
    # logout
    # elsif ( $Param{Action} eq 'Logout' ) {
    
        my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
    
        # check session id
        if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) {
            $Self->_logoutCAS();
            return 1;
        #...  if 分支结束后也要掉用一次,这里省略不表
    

    考虑没有系统session的情况:

    # show login site
    elsif ( !$Param{SessionID} ) {
        
        # use CAS Auth login
        # 2017-11-13 by Carl.
    
        my $User = $Self->_loginCAS();
    
        # login is successful
        my %UserData = $UserObject->GetUserData(
            User  => $User,
            Valid => 1
        );
    

    这样保证无session的时候必先验证 cas,验证通过才有session。退出系统清除session
    然而,这里出现一个bug。因为otrs的session也是写在本地cookie,而调用logout的时候直接重定向到了cas-server。此时本地cookie未被清除。这就是为什么logout方法中需要输出 Set-cookie文件头的原因。(注:此处表现了对http协议还不够熟悉。服务端的logout请求只与cas-client端的web服务器交互,无法影响浏览器客户端的cookie!)

    小结

    代码仍旧很简单。难点在于除了要分析otrs的登录流程,还需要自己构造perl客户端。
    不过正由于如此,反而对cas认证机制有了更清晰的认识。之前虽然修改了几个系统,但是对cas的过程并没有认真分析。
    客户端验证cas的时候,先向服务器验证本地isAuthenticated,未成功则发起登录请求。这是第一次302
    服务端发现已有用户登录,直接根据客户端请求中的service参数,获取重定向地址,并附带 Server-token 。这是第二次302
    客户端收到 server-token 之后,拿这个ST 向服务端发起 curl 请求获取xml用户信息。此时需要一并传入之前发起认证时的客户端url(未带st参数)。
    这时如果直接使用 REQUEST_URI (携带了ST参数) 将导致认证失败。
    成功的xml:

    失败的xml:

    php-client 的做法是获取用户信息后写入session, 然后直接再次重定向到没有ST参数的页面. 这是第三次302

    如何才能保留cookie机制的同时认证cas?
    我的想法是在接受服务端的 logoutRequest的时候清楚数据库中的session(或使之过期)。
    但是目前发现,在其他客户端推出的时候,好像收不到服务端的登出通知?
    日志中也无法得知
    继续探索

    ============================================================================

    【2017-11-17】修复客户端同步退出问题。

    查看Apache的access_log发现,其实cas-server有发送 logoutRequest:

    经过一个下午的调试研究,终于解决了同步退出的问题,下面是过程:

    Otrs的session机制

    用户登录后,会在后台sessions表创建大量session变量,当用户推出后,会根据session_id 清空所有变量。

    解决方案

    既然不能清除浏览器的cookie,那么我们可以清除sessions表里的session_id,从而使从浏览器读取的cookie无法验证通过,达到退出登录的目的。
    研究cas-server的POST请求发现,只有一个参数:
    logoutRequest: <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-2105-Kha1YoKgOCpxoCHuPf7qrdYVMTHvH4HYRVK" Version="2.0" IssueInstant="2017-11-17T16:20:03Z"><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID><samlp:SessionIndex>ST-2105-6fyJS6MtEhRUKsBqECQG-cas.cloud.com</samlp:SessionIndex></samlp:LogoutRequest>
    唯一有用的就是里面的ST token。这个在登录的时候也能获取到。
    然后需要做的就是吧这个ST 与 Otrs产生的 SessionID 绑定。
    我们在登录成功后,创建一个 CasServerToken的 session 变量。然后在退出的时候根据这个变量值查找对应的session_id, 删除这个 session_id.

    代码过程

    1) 登录的时候保存ST my ($User, $CASST) = $Self->_loginCAS();,在后面创建了SessionID的位置加上:

        # login cas. save cas-ST
        # 2017-11-17 by Carl.
        $SessionObject->UpdateSessionID(
            SessionID => $NewSessionID,
            Key       => 'CasServerToken',
            Value     => $CASST,
        );
    

    2) 处理同步登出请求:

    # Handle CAS-Server logout request.
    # 2017-11-13 by Carl.
    if ( $ParamObject->GetParam(Param => 'logoutRequest') ){
        # handle logout request
    
        # read CAS ST ticket from logout request.
        my $CasServerToken = $ParamObject->GetParam(Param => 'logoutRequest');
        $CasServerToken =~ s/^.*<samlp:SessionIndex>(ST-.*)</samlp:SessionIndex>.*$/$1/;
        # find current session ID.
        my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
        $DBObject->Prepare(
            SQL => "
                SELECT session_id
                FROM sessions
                WHERE data_key = 'CasServerToken' AND data_value = ?
                LIMIT 1 ",
            Bind => [ $CasServerToken ],
        );
        my @Row = $DBObject->FetchrowArray();
        if (my $CasSessionID = $Row[0]) {
            # Remove session ID.
            $SessionObject->RemoveSessionID( SessionID => $CasSessionID );
        }
        exit 0;
    }
    

    =============================================================
    至此,otrs 的cas接入完成。

  • 相关阅读:
    docker安装minio
    详解nohup /dev/null 2>&1 含义的使用
    CentOS7系统更换yum Repo源
    centos7运行yum报如下提示:Run "yum repolist all" to see the repos you have
    linux安全篇:禁止频繁访问的ip访问nginx
    Nginx 添加防爬虫
    Nginx 加载conf.d (内文件***.conf)
    rabbitMq消费死循环
    RabbitMq安装(单点与集群)rabbitMq以及状态查询
    rabbitMq内存与磁盘分配问题
  • 原文地址:https://www.cnblogs.com/dapianzi/p/7834250.html
Copyright © 2011-2022 走看看