简体   繁体   English

无密码 LDAP 登录并在 PHP 中使用 Kerberos 票证获取用户信息

[英]passwordless LDAP login and get user information using Kerberos ticket in PHP

I'm trying to implement SSO on some intranet sites in our company using FreeIPA/Kerberos.我正在尝试使用 FreeIPA/Kerberos 在我们公司的一些 Intranet 站点上实施 SSO。 But information on this topic is very thin.但是关于这个主题的信息非常少。

I have three machines running in my test network:我的测试网络中运行了三台机器:

  1. FreeIPA v4.9.8 Server on Centos 8 Stream Centos 8 Stream 上的 FreeIPA v4.9.8 服务器
  2. Web Server (Apache v2.4.53, PHP v7.4.28) on Debian 11 Debian 11 上的 Web 服务器(Apache v2.4.53、PHP v7.4.28)
  3. Xubuntu 22.04 Client with Kinit and Firefox带有 Kinit 和 Firefox 的 Xubuntu 22.04 客户端

Kinit, Unix Login and Apache Kerberos Auth work. Kinit、Unix Login 和 Apache Kerberos Auth 工作。 The Firefox browser on the client system can log on to the FreeIPA WebConfig without a password (using Kerberos Ticket).客户端系统上的 Firefox 浏览器无需密码即可登录到 FreeIPA WebConfig(使用 Kerberos Ticket)。 I would now like to transfer this function to our intranet pages.我现在想将此功能转移到我们的内联网页面。 Up until now, the login to these pages has been based on a conventional LDAP login.到目前为止,这些页面的登录都是基于传统的 LDAP 登录。 With minor adjustments to the login script, the user can now log on to the new FreeIPA server.通过对登录脚本的细微调整,用户现在可以登录到新的 FreeIPA 服务器。 However, he still needs his password for this, which should actually no longer be necessary thanks to the Kerberos ticket.然而,他仍然需要他的密码,这实际上应该不再需要多亏了 Kerberos 票证。

The question is, what does a passwordless login look like?问题是,无密码登录是什么样的?

a functioning snippet of the login script:登录脚本的功能片段:

<?php
$username = $_SERVER['PHP_AUTH_USER'];
$password = 'password';

$ldap_rdn  = 'uid='.$username.',cn=users,cn=accounts,dc=exampletest,dc=de';
$ldap_server = ldap_connect('ldap://ipa.exampletest.de:389');

ldap_set_option($ldap_server, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap_server, LDAP_OPT_REFERRALS, 0);

if ($ldap_server) {
  $ldap_bind = @ldap_bind($ldap_server, $ldap_rdn, $password);
  if ($ldap_bind) {
    $search = array("uid","givenname","sn","mail","uidnumber","gidnumber");
    $result = ldap_search($ldap_server, $ldap_rdn, "mail=$username*", $search);
    $info = ldap_get_entries($ldap_server, $result);

    print_r($info);
  }
}
?>

Now I have two ideas:现在我有两个想法:

  1. I could use ldap_sasl_bind() instead of ldap_bind() but this function is not documented on php.net ( https://www.php.net/manual/en/function.ldap-sasl-bind.php ).我可以使用 ldap_sasl_bind() 而不是 ldap_bind() 但这个函数没有记录在 php.net ( https://www.php.net/manual/en/function.ldap-sasl-bind.php ) 上。 If anyone has any idea how to work with this function I would greatly appreciate it.如果有人知道如何使用此功能,我将不胜感激。
  2. If I could somehow run ldap_search() without a password to get the user information (full name, email, etc.) I'd be happy too.如果我能以某种方式在没有密码的情况下运行 ldap_search() 来获取用户信息(全名、电子邮件等),我也会很高兴。

Many thanks in advance.提前谢谢了。

EDIT:编辑:

The web server VM and the client VM are both initialized via "ipa-client-install". Web 服务器 VM 和客户端 VM 都通过“ipa-client-install”进行初始化。 In addition, the web server has registered the apache service (ipa service-add HTTP/ebook.exampletest.de).另外,web服务器已经注册了apache服务(ipa service-add HTTP/ebook.exampletest.de)。

The apache config also reflected this: apache 配置也反映了这一点:

<Directory /var/www/ebook/>
        AuthType                GSSAPI
        AuthName                "eBook Login"
        GssapiCredStore         keytab:/etc/apache2/http.keytab
        GssapiAllowedMech       krb5
        GssapiBasicAuthMech     krb5
        GssapiImpersonate       On
        GssapiDelegCcacheDir    /run/apache2/clientcaches
        GssapiLocalName         On
        
        # for production set to on:
        GssapiSSLonly           Off
        GssapiNegotiateOnce     Off
        
        GssapiUseSessions       On
        Session                 On
        SessionCookieName       gssapi_session path=/private;httponly;secure;
        Require                 valid-user
    </Directory>

And as I already mentioned, user authentication seems to works this way ( client (own ticket) > web service (own ticket) > ipa server ).正如我已经提到的,用户身份验证似乎以这种方式工作( client (own ticket) > web service (own ticket) > ipa server )。 Otherwise the apache server would not return my ldap/kerberos-username.否则 apache 服务器不会返回我的 ldap/kerberos 用户名。 Or am I missing something important here?还是我在这里遗漏了一些重要的东西? Is there another way to enforce this kind of authentication?是否有另一种方法来强制执行这种身份验证?

output of: <?php print_r($_SERVER) ?> (snipped) <?php print_r($_SERVER) ?>的输出(截断)

[GSS_MECH] => Negotiate/krb5
[GSS_NAME] => test@EXAMPLETEST.DE
[REMOTE_USER] => test
[AUTH_TYPE] => Negotiate
[PHP_AUTH_USER] => test

However, he still needs his password for this, which should actually no longer be necessary thanks to the Kerberos ticket.然而,他仍然需要他的密码,这实际上应该不再需要多亏了 Kerberos 票证。

Make sure your web server has the right Kerberos ticket.确保您的 Web 服务器具有正确的 Kerberos 票证。

Normally Kerberos auth only transfers a ticket valid only for that server, not a blanket "everything" ticket.通常 Kerberos 身份验证仅传输仅对该服务器有效的票证,而不是一揽子“一切”票证。 When the client authenticates to your webapp, all you get is a ticket for HTTP/webapp.example.tld and you can't really use it to access LDAP on behalf of the user.当客户端向您的 webapp 进行身份验证时,您得到的只是HTTP/webapp.example.tld的票证,您不能真正使用它代表用户访问 LDAP。

If you need to access LDAP on behalf of the user, a few options exist:如果您需要代表用户访问 LDAP,则存在一些选项:

  • The webapp could have its own credentials to the LDAP directory. webapp 可以拥有自己的 LDAP 目录凭据。 This might be the easiest approach.这可能是最简单的方法。 The webapp could either use standard password bind, or Kerberos (SASL) bind with its own tickets acquired from a keytab. webapp 可以使用标准密码绑定,也可以使用 Kerberos (SASL) 绑定从 keytab 获取的自己的票证。

    • LDAP also supports "impersonation", where the webapp would use its own credentials to authenticate but also specify an "authorization ID" (authzid) that determines which account's privileges you will get. LDAP 还支持“模拟”,其中 web 应用程序将使用自己的凭据进行身份验证,但指定一个“授权 ID”(authzid)来确定您将获得哪个帐户的权限。

      For example, if you authenticate as 'webapp' but specify the authzid 'myuser' (and if the LDAP server allows this), then you will get exactly the privileges that 'myuser' would normally have – not those of 'webapp'.例如,如果您以 'webapp' 身份进行身份验证,但指定 authzid 'myuser'(并且如果 LDAP 服务器允许这样做),那么您将获得 'myuser' 通常拥有的权限 - 而不是 'webapp' 的权限。

  • The webapp's HTTP Negotiate (SPNEGO) authentication could have "delegation" enabled. webapp 的 HTTP 协商 (SPNEGO) 身份验证可以启用“委托”。 Delegation does transfer the main krbtgt ticket to the web server, which will then put it in a temporary ticket cache and make available to your webapp's environment.委托确实将主krbtgt票证传输到 Web 服务器,然后将其放入临时票证缓存中,并提供给您的 Web 应用程序环境。

    Delegation has a few problems, however:然而,委派有一些问题:

    1. It makes each HTTP request slower, as the client has to request a new krbtgt ticket with the "forwarded" flag (unless the web server can use eg cookies to avoid requesting Negotiate auth for further requests, like mod_auth_gssapi with the "Session" mode).它使每个 HTTP 请求变慢,因为客户端必须使用“转发”标志请求新的krbtgt票证(除非 Web 服务器可以使用例如 cookie 来避免为进一步的请求请求协商身份验证,例如带有“会话”模式的 mod_auth_gssapi) .

    2. It requires the webapp to be highly trusted, as it will be storing wildcard tickets for every user accessing it (including admins) – even if the webapp itself is trusted to not abuse them, they could still get stolen from the server.它要求 webapp 是高度可信的,因为它将为每个访问它的用户(包括管理员)存储通配符票证——即使 webapp 本身被信任不会滥用它们,它们仍然可能从服务器中被盗。

    3. Most Kerberos-using APIs (including ldap_sasl_bind()) will expect the KRB5CCNAME environment variable to point at the ticket cache.大多数使用 Kerberos 的 API(包括 ldap_sasl_bind())都期望KRB5CCNAME环境变量指向票证缓存。 But environment variables are process-wide, so they can leak across unrelated requests whenever PHP reuses the same process (or even worse, if you use mod_php to run the webapp inside the Apache process).但是环境变量是进程范围的,所以只要 PHP 重用同一个进程(或者更糟糕的是,如果你使用 mod_php 在 Apache 进程中运行 webapp),它们就会在不相关的请求之间泄漏。

    In AD, this is known specifically as "unconstrained delegation", as AD introduces other variants.在 AD 中,这被特别称为“无约束委派”,因为 AD 引入了其他变体。

  • The webapp could use S4U2Proxy aka "Constrained delegation" to create tickets on behalf of a user, for a certain limited set of services (eg FreeIPA could restrict it to only accessing ldap/foo.example.com ). webapp 可以使用 S4U2Proxy 又名“约束委派”来代表用户为一组有限的服务创建票证(例如,FreeIPA 可以将其限制为仅访问ldap/foo.example.com )。

    This is somewhat complex (PHP has no APIs for this – you'd probably need to spawn kinit with the right flags), and still has the same potential problem with KRB5CCNAME leaking across requests.这有点复杂(PHP 没有用于此的 API——您可能需要使用正确的标志生成kinit ),并且仍然存在与 KRB5CCNAME 跨请求泄漏相同的潜在问题。

I could use ldap_sasl_bind() instead of ldap_bind() but this function is not documented on php.net ( https://www.php.net/manual/en/function.ldap-sasl-bind.php ).我可以使用 ldap_sasl_bind() 而不是 ldap_bind() 但这个函数没有记录在 php.net ( https://www.php.net/manual/en/function.ldap-sasl-bind.php ) 上。 If anyone has any idea how to work with this function I would greatly appreciate it.如果有人知道如何使用此功能,我将不胜感激。

For regular Kerberos authentication, the usage looks like this:对于常规的 Kerberos 身份验证,用法如下所示:

ldap_sasl_bind($conn, null, null, "GSSAPI");

That's all.就这样。 The GSSAPI SASL mechanism expects the environment to already have Kerberos tickets available (eg via $KRB5CCNAME, or via gss-proxy), and it will authenticate using whatever ticket it finds there. GSSAPI SASL 机制期望环境已经有可用的 Kerberos 票证(例如,通过 $KRB5CCNAME 或通过 gss-proxy),并且它将使用它在那里找到的任何票证进行身份验证。

If you wanted to use impersonation (assuming that's set up in the LDAP server), you'd have to specify the authz_id:如果您想使用模拟(假设已在 LDAP 服务器中设置),则必须指定 authz_id:

ldap_sasl_bind($conn, null, null, "GSSAPI", null, null, $theuser);

Most ldap_*() PHP functions are direct wrappers around the C libldap library, so its documentation may be useful as a partial reference.大多数ldap_*() PHP 函数都是 C libldap 库的直接包装器,因此它的文档可能作为部分参考有用。

$result = ldap_search($ldap_server, $ldap_rdn, "mail=$username*", $search);

It seems your example already specifies the user's exact DN, so there seems to be no need for the additional filtering by mail – just use objectClass=* when reading a specific DN.您的示例似乎已经指定了用户的确切 DN,因此似乎不需要通过mail进行额外过滤 - 只需在读取特定 DN 时使用objectClass=*即可。 Also, when you want to read a specific DN, use ldap_read() to make a 'base' search instead of subtree search.此外,当您要读取特定 DN 时,请使用ldap_read()进行“基本”搜索而不是子树搜索。

And as I already mentioned, user authentication seems to works this way (client (own ticket) > web service (own ticket) > ipa server).正如我已经提到的,用户身份验证似乎以这种方式工作(客户端(自己的票证)> Web 服务(自己的票证)> ipa 服务器)。 Otherwise the apache server would not return my ldap/kerberos-username.否则 apache 服务器不会返回我的 ldap/kerberos 用户名。

No, that's not how it works.不,这不是它的工作原理。 Your username (ie the client Kerberos principal) is stored in the client ticket, so the web server immediately knows it upon decrypting the ticket, without having to talk to IPA at all.您的用户名(即客户端 Kerberos 主体)存储在客户端票证中,因此 Web 服务器在解密票证后立即知道它,而根本无需与 IPA 对话。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM