从 FacesContext 获取 LDAPContext 的最简单方法是什么?

What is the easiest way to get an LDAPContext from a FacesContext?

我有一个从 JSF 页面调用的 Java doLogin() 方法,该方法从用户那里获取 ID (String netId) 和密码 (String password)。 doLogin() 使用 netId 作为 Active Directory 登录中的主体启动身份验证。之后,我想从保护我的应用程序的目录中获取主体名称以外的其他属性。

我的安全性是在容器中配置的并且可以正常工作,因此

HttpSession ses = FacesContext.getCurrentInstance().getExternalContext().getSession (false);
HttpServletRequest req = FacesContext.getCurrentInstance().getExternalContext().getRequest();

req.login(netID, password);

成功并且

req.getUserPrincipal().getName();

returns 用户的 netID。但是,我的应用程序仅使用 netId 进行身份验证。访问另一个数据库的应用程序的其他部分需要其他属性(例如 commonName)。我想做类似

的事情
usefulLDAPobj = *getLDAPSession from "somewhere" in the HTTP Session, the FacesContext or some other available object*

String cn = usefulLDAPobj.getAttributeFromProfile ("cn");

ses.setAttribute("username", cn);

然后在我的 Hibernate ORM 中使用存储在会话中的用户名。

我知道头脑简单的人 usefulLDAPobj.getAttributeFromProfile ("cn") 会更复杂,但如果我能找到一个让我访问 LDAP 目录的起点,我可以填写它。

由于容器正在设置一个明显的 LDAP 连接,我觉得 必须 是我使用它而无需手动构建 LdapContext 的一种方式以编程方式;这将要求代码知道 Web 服务器 (JBoss EAP 6.2) 已经知道的所有 LDAP server / bind-DN / bind-password configuration(来自 standalone.xml 中定义的 <login-module>)。例如,getUserPrincipal()isUserInRole() 等方法需要访问我想要访问的同一个目录配置文件。

所以我的问题是:有没有办法从 FacesContext 或 HTTPServletRequest 或任何可从 HTTPServlet 访问的对象获取 LDAP 连接或上下文?

What is the easiest way to get an LdapConext from a FacesConext?

根本就没有办法,更别说简单的方法了。 JSF 不假定存在 LDAP 服务器,也不提供任何与 LDAP 相关的 API。

Since there is an obvious LDAP connection being set up by the container

当您登录时。不是永久的。如果根本没有 LDAP 服务器。而且 JSF 不知道容器是如何登录的。

I feel there must be a way ...

没有。

我认为这个问题的一个有用答案是 没有办法直接从 FacesContext 获得 LDAPContext,而是通过编写特定于容器的登录模块和 Principal class 您可以通过 HttpServletRequest 传递额外的数据,这些数据是通过 FacesContext.

获得的

我将在此处提供我的解决方案的详细信息,因为即使它与 FacesContext 没有直接关系,它也提供了我在问题正文中要求的内容,这是获得更多信息的一种方式来自 LDAP 配置文件的用户数据,同时避免需要创建一个完整的单独 LDAPContext

我特别想要的是 CN,我能够从 DN 中解析出来,而无需进行额外的搜索。如果我需要任何其他数据,我假设我可以使用下面 findUserDN() 中的 ctx 获得。

我想我正在使用此解决方案使我的应用程序依赖于 JBoss,如果这不是我们想要的,我会搜索一个 JBoss 独立的登录模块 class 来扩展(不知道那是容易、困难还是不可能。

这是我的解决方案:

  1. 覆盖 AdvancedADLoginModule 中的 findUserDN (LdapContext ctx)

    package ca.mycompany.myapp.jboss;
    
    import java.security.Principal;
    
    import javax.naming.ldap.LdapContext;
    import javax.security.auth.login.LoginException;
    
    import org.jboss.security.negotiation.AdvancedADLoginModule;
    
    public class NameFetchingADLoginModule extends AdvancedADLoginModule
    
        @Override
        protected String findUserDN(LdapContext ctx) throws LoginException
        {
            String lclUserDN = super.findUserDN(ctx);
    
            Principal principal = getIdentity();
    
            if (principal instanceof PrincipalWithDisplayName)
            {
                String displayName = lclUserDN.substring(3, lclUserDN.indexOf(','));
                ((PrincipalWithDisplayName) principal).setDisplayName (displayName);
            }
    
            return lclUserDN;
        }
    }
    
  2. 扩展 Principal 以提供 displayName 属性

    package ca.mycompany.myapp.jboss;
    
    import java.io.Serializable;
    import java.security.Principal;
    
    public class PrincipalWithDisplayName implements Serializable, Principal
    {
        private static final long serialVersionUID = 1L;
        private final String name;
    
        // additional attribute provided by this subclass
        private String displayName;
    
        public PrincipalWithDisplayName(final String name) {
            this.name = name;
        }
    
        // new and overriding getters and setters, equals() and hashCode() removed for brevity
    }
    
  3. 在 doLogin() 方法中使用新的登录模块和 Principal

片段:

    String displayName = "";
    HttpSession ses = FacesContext.getCurrentInstance().getExternalContext().getSession (false);
    HttpServletRequest req = FacesContext.getCurrentInstance().getExternalContext().getRequest();

    try {           
        req.login(userName, password); // this throws an exception if authentication fails

        Principal lclUser = req.getUserPrincipal();
        if (lclUser instanceof PrincipalWithDisplayName)
        {
            displayName = ((PrincipalWithDisplayName) lclUser).getDisplayName ();
        }

        // get Http Session and store username
        //
        HttpSession session = HttpUtil.getSession();
        sess.setAttribute("username", displayName);
        ...
  1. standalone.xml 中配置 JBoss EAP 6.2,以使用新的 classes

片段:

<subsystem xmlns="urn:jboss:domain:security:1.2">
    <security-domains>
        <security-domain name="company_ad" cache-type="default">
            <authentication>
                <login-module code="ca.mycompany.myapp.jboss.NameFetchingADLoginModule" flag="required">
                    <module-option name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory"/>
                    <module-option name="java.naming.provider.url" value="ldap://servernm.mycompany.tst:389"/>
                    <module-option name="java.naming.security.authentication" value="simple"/>
                    <module-option name="bindDN" value="CN=AuthGuy,OU=Accounts,OU=Company User Accounts,DC=company,DC=tst"/>
                    <module-option name="bindCredential" value="Snowden1"/>
                    <module-option name="baseCtxDN" value="OU=Company User Accounts,DC=company,DC=tst"/>
                    <module-option name="baseFilter" value="(sAMnetID={0})"/>
                    <module-option name="searchScope" value="SUBTREE_SCOPE"/>
                    <module-option name="allowEmptyPassword" value="false"/>
                    <module-option name="rolesCtxDN" value="OU=Company User Accounts,DC=company,DC=tst"/>
                    <module-option name="roleFilter" value="(sAMAccountName={0})"/>
                    <module-option name="roleAttributeID" value="memberOf"/>
                    <module-option name="roleAttributeIsDN" value="true"/>
                    <module-option name="roleNameAttributeID" value="cn"/>
                    <module-option name="recurseRoles" value="1"/>
                    <module-option name="principalClass" value="ca.mycompany.myapp.jboss.PrincipalWithDisplayName"/>
                </login-module>
            </authentication>
        </security-domain>
    </security-domains>
</subsystem>