我们需要通过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设置将发挥重要作用,因此我将从以下页面复制检查表:
- 使用主机名而非IP集成访问服务器
- IE中的Windows身份验证已启用,主机在中受信任
- Firefox服务器不是浏览器的本地服务器客户端的Kerberos
- 系统已通过域控制器身份验证
http://wiki.eclipse.org/Jetty/Howto/Spnego
如果可以对外部用户进行身份验证,则可能需要将用户名(至少)存储在应用程序数据库中。
您可以使用JNDI通过LDAP的AD实现来获取用户组信息(这也需要web.xml中的一些标准条目)。
您可以通过JCIFS对intranet上的内部AD用户使用NTLM身份验证。
基于此假设:
- 搜索NTLMFilter类,有一个通用代码可以使用
-
使用在步骤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; }
-
对于外部用户有一个正常的用户名/密码验证:像这个
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; }
您可能需要一个通用的方法来验证内部/外部用户(调用以上两个方法)并相应地返回结果。
希望它能有所帮助,祝你好运!