在可用的情况下,使用单一登录通过Active Directory进行身份验证的最佳方式是什么



我们需要通过Active Directory进行身份验证。我们希望域内的Windows用户可以在不输入用户名和密码的情况下进行身份验证(单一登录),但外部用户(或不使用Internet Explorer的用户)也可以插入用户名和密码并登录。

我们还需要在用户所在的群组中举手,因为这将改变该用户在我们网站上看到的内容。

我们使用Java和Jetty作为我们的应用程序服务器,并在Windows中进行开发,但我们的服务器将是Linux。

谢谢!

根据@Akber的建议,您可以使用IP范围。您需要一个将使用远程地址或X-Forwarded-For标头的公共端点,使用可以测试其是否在intranet范围内的IP,这就是10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/12

如果IP在intranet范围内,则可以重定向到Apache代理(稍后将对此进行详细介绍)。如果IP超出范围,则重定向到具有漂亮表单的端点。

集成身份验证终结点

带有mod_auth_kerb的Apache是在Linux中为我们提供这种场景的唯一方法之一。您可以将apache配置为kerberos代理,它将协商kerberos,然后用头调用后端。这是一个配置示例:

ProxyPass        / http://localhost:9005/ #your backend
ProxyPassReverse / http://localhost:9005/ #your backend
ProxyPreserveHost On
## Rewrite rules
RewriteEngine On
RewriteCond %{LA-U:REMOTE_USER} (.+)
RewriteRule . - [E=RU:%1]
## Request header rules
## as per http://httpd.apache.org/docs/2.2/mod/mod_headers.html#requestheader
RequestHeader set X-Forwarded-User %{RU}e
<Location />
AuthName "Kerberos Login"
AuthType Kerberos
Krb5Keytab /path/to your keytab/HTTP.keytab
KrbAuthRealm DOMAIN.LOC
KrbMethodNegotiate on
KrbSaveCredentials off
KrbVerifyKDC off
KrbServiceName HTTP/YOURAPP.AD2008R2.LOC
Require valid-user
</Location>

然后,后端将收到X-Forwarded-User,您可以使用LDAP递归地获取完整的配置文件和组。

注意有一个/path/to your keytab/HTTP.keytab,这个文件应该是从绑定到域的Windows机器生成的。

表单身份验证终结点

这是由你的应用程序直接处理的,一旦你收到用户名和密码,你就必须尝试使用LDAP协议"绑定"到AD,然后你必须递归地获取用户配置文件和组。

这种方法和替代解决方案的缺点

这可能看起来很简单,但实际上涉及到大量的工作,不仅是代码,还有维护。还有另外两种解决方案可能适用于您的情况,但需要部署另一种产品;

  • ADFS:是一款来自Microsoft的产品,可以部署在Windows Server(IIS)中,使用AD进行身份验证,并与WS-Federation或SAML进行对话
  • Auth0:可以在本地部署,它作为虚拟设备(linux)提供。它可以使用任何身份提供者进行身份验证,当然也包括AD。我们做了一些类似于我在这里为AD描述的事情,但从您的应用程序中,您不需要做任何事情,只需使用OAuth库或JWT验证库即可

免责声明:我为Auth0工作。

对于组个性化,web应用程序需要知道用户来自哪里(Intranet或Internet),这可以通过Apache中的标头、IP范围配置等来完成——这取决于您的详细配置和代码。

对于Intranet SSO,您似乎需要SPNEGO,链接如下。对于Intranet SSO,您公司的管理策略和Internet Explorer设置将发挥重要作用,因此我将从以下页面复制检查表:

  1. 使用主机名而非IP集成访问服务器
  2. IE中的Windows身份验证已启用,主机在中受信任
  3. Firefox服务器不是浏览器的本地服务器客户端的Kerberos
  4. 系统已通过域控制器身份验证

http://wiki.eclipse.org/Jetty/Howto/Spnego

如果可以对外部用户进行身份验证,则可能需要将用户名(至少)存储在应用程序数据库中。

您可以使用JNDI通过LDAP的AD实现来获取用户组信息(这也需要web.xml中的一些标准条目)。

您可以通过JCIFS对intranet上的内部AD用户使用NTLM身份验证。

基于此假设:

  1. 搜索NTLMFilter类,有一个通用代码可以使用
  2. 使用在步骤1中创建的NTLMFilter实现,通过JCIFS使用NTLM身份验证在intranet内为用户进行单次登录:类似于此

    公共静态布尔身份验证UsingJCIF(字符串用户名,字符串密码){

    UniAddress uniaddress = null;
    String _methodName = "authenticateUsingJCIF";
    try {
    uniaddress = UniAddress.getByName(PropertyUtils
    .getProperty(AUTHENTICATION_SERVER_URL));
    NtlmPasswordAuthentication ntlmpasswordauthentication = new NtlmPasswordAuthentication(PropertyUtils.getProperty(AUTHENTICATION_SERVER_DOMAIN),username, password); //You can have your own method to read properties, I've just delegated to a generic utils
    SmbSession.logon(uniaddress, ntlmpasswordauthentication);
    logger.info("INTERNAL User authenticated successfully against AD");
    } catch (UnknownHostException e) {
    logger.error(e.toString(), e);
    return false;
    } catch (SmbException e) {
    logger.error(e.toString(), e);
    return false;
    } catch (Exception e) {
    logger.error(e.toString(), e);
    return false;
    }
    return true;
    }
    
  3. 对于外部用户有一个正常的用户名/密码验证:像这个

    private字符串authenticateExternalUser(字符串id,YourUserNamePasswordAuthenticationSource authSource){字符串状态消息=null;

    logger
    .info("Performing credential verification for external user against INTERNAL DB");
    //Create or utilize your own handlers to validate plain/encoded credentials against internal db
    CredentialVerificationResult returnCode = _handler
    .verifyInternalCredential(id, authSource.getPassword());
    logger.info("Logging value of return code" + returnCode.getMessage());
    if (returnCode == CredentialVerificationResult.SUCCESS) {
    statusMessage = CREDENTIAL_VERIFICATION_SUCCESS;
    } else if (returnCode == CredentialVerificationResult.BAD_USER_ID) {
    statusMessage = BAD_USER_ID;
    } else if (returnCode == CredentialVerificationResult.WAIT_TO_RETRY) {
    statusMessage = WAIT_TO_RETRY;
    } else if (returnCode == CredentialVerificationResult.CREDENTIAL_LOCKED) {
    statusMessage = CREDENTIAL_LOCKED;
    } else if (returnCode == CredentialVerificationResult.PASSWORD_MISMATCH) {
    statusMessage = PASSWORD_MISMATCH;
    }
    return statusMessage;
    }
    

您可能需要一个通用的方法来验证内部/外部用户(调用以上两个方法)并相应地返回结果。

希望它能有所帮助,祝你好运!

最新更新