作者:高正_飞翔之殇_826 | 来源:互联网 | 2023-08-31 11:38
OTRS是用Perl写的一个工单邮件系统,非常强大。登录流程流程图略过otrs没有像discuz和zabbix类似的游客登录状态,这样处理起来逻辑分支少一些。不过还是考虑用otrs的s
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\n";
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\nContent-Type: text/html; charset=UTF-8;\nStatus: 302 Found\nLocation: %s\n\n", $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\n\n";
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
文件头的原因。
小结
代码仍旧很简单。难点在于除了要分析otrs的登录流程,还需要自己构造perl客户端。
不过正由于如此,反而对cas认证机制有了更清晰的认识。之前虽然修改了几个系统,但是对cas的过程并没有认真分析。
客户端验证cas的时候,先向服务器验证本地isAuthenticated
,未成功则发起登录请求。这是第一次302
服务端发现已有用户登录,直接根据客户端请求中的service
参数,获取重定向地址,并附带 Server-token 。这是第二次302
客户端收到 server-token 之后,拿这个ST 向服务端发起 curl 请求获取xml用户信息。此时需要一并传入之前发起认证时的客户端url(未带st参数)。这时如果直接使用 REQUEST_URI (携带了ST参数) 将导致认证失败。php-client 的做法是获取用户信息后写入session, 然后直接再次重定向到没有ST参数的页面. 这是第三次302
如何才能保留COOKIE机制的同时认证cas?
我的想法是在接受服务端的 logoutRequest的时候清楚数据库中的session(或使之过期)。
但是目前发现,在其他客户端推出的时候,好像收不到服务端的登出通知?
日志中也无法得知
继续探索