在可用的情况下通过单点登录通过Active Directory进行身份验证的最佳方式是哪种?

时间:2013-12-26 18:25:53

标签: java authentication active-directory jetty single-sign-on

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

我们还需要在用户所属的群组中发挥作用,因为这将改变用户在我们网站上可以看到的内容。

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

谢谢!

3 个答案:

答案 0 :(得分:3)

根据@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工作

答案 1 :(得分:0)

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

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

  1. 使用主机名而不是IP集成
  2. 访问服务器
  3. IE中的Windows身份验证已启用,主机信任
  4. Firefox服务器不是浏览器的本地服务器客户端的Kerberos
  5. 系统已通过域控制器验证
  6. http://wiki.eclipse.org/Jetty/Howto/Spnego

答案 2 :(得分:0)

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

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

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

基于这个假设:

  1. 搜索NTLMFilter类,有一个可以使用的公共代码
  2. 使用在步骤1中创建的此NTLMFilter实现,通过JCIFS使用NTLM身份验证为Intranet中的用户进行单点登录:像这样

    public static Boolean authenticateUsingJCIF(String username,String password){

            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 String authenticateExternalUser(String id,             YourUserNamePasswordAuthenticationSource authSource){         String statusMessage = 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;
    }
    
  4. 您可能希望使用一种通用方法来验证内部/外部用户(以调用上述两种方法)并相应地返回结果。

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