Apache Shiro LDAP 配置(两步验证)

Posted

技术标签:

【中文标题】Apache Shiro LDAP 配置(两步验证)【英文标题】:Apache Shiro LDAP Configuration (2 Step Authentication) 【发布时间】:2017-02-01 07:01:54 【问题描述】:

我正在使用以下内容开发网络应用程序:

Apache Shiro 1.2.4 雄猫 8 Java 8

我正在尝试通过 LDAP 领域对用户进行身份验证。

我使用以下值使用 JXplorer 测试了登录:

BaseND:ou=students,ou=users,o=data UserDN/BindDN:cn=我的专有名称,ou=students,ou=users,o=data JXplorer 还有一个我导入的证书

现在是我的问题:

我必须不是基于cn 而是基于uid 对用户进行身份验证。 但是这行不通。

我公司的 LDAP 管理员建议的工作流程:

我有一个系统用户(据我了解)应该连接到 LDAP 服务器,根据给定的 uid 搜索用户并返回该用户的 cn

在第二步中,应该可以使用他的cn 对用户进行身份验证,就像在 JXplorer 中完成(成功)一样。


我的 shiro.ini 配置对于我的测试 LDAP 看起来像这样(不是我现在尝试验证的那个)

ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = cn=0,ou=People,dc=maxcrc,dc=com
ldapRealm.contextFactory.url = ldap://localhost:389

我现在面临的问题是,我找不到有关如何配置我的 shiro.ini 文件的信息,以从上面计算建议的工作流程。

问题 #1

可以使用标准的org.apache.shiro.realm.ldap.JndiLdapRealm(如何配置)?特别是对于证书的集成,我找不到任何合适的东西。

问题 #2

我是否需要为此“两步身份验证”创建自定义 JndiLdapRealm?这样的 Realm 是什么样子的?

【问题讨论】:

【参考方案1】:

我找到了一个解决方案,那就是从JndiLdapRealm重载getUserDN()方法

现在看起来像这样:

/**
* Addition to standard shiro code
* User logs in with his @link principal (username) but login needs to be performed with his cn instead
* The addition is an ldap query to get the users cn from the ldap server and store it (instead of the given principal) in the return String
* @return UserDN with user uid (cn) instead of principal (username)
*/
@Override
protected String getUserDn( final String principal ) throws IllegalArgumentException, IllegalStateException 

    if (!StringUtils.hasText(principal)) 
        throw new IllegalArgumentException("User principal cannot be null or empty for User DN construction.");
    
    String prefix = getUserDnPrefix();
    String suffix = getUserDnSuffix();
    if (prefix == null && suffix == null) 
        log.debug("userDnTemplate property has not been configured, indicating the submitted " +
                  "AuthenticationToken's principal is the same as the User DN.  Returning the method argument " +
                  "as is.");
        return principal;
    

    int prefixLength = prefix != null ? prefix.length() : 0;
    int suffixLength = suffix != null ? suffix.length() : 0;
    StringBuilder sb = new StringBuilder(prefixLength + principal.length() + suffixLength);
    if (prefixLength > 0) 
        sb.append(prefix);
    

    /*############################################################################################
    * ADDITION TO STANDARD SHIRO CODE 
    * User logs in with his @link principal (username) but login needs to be performed with his cn instead
    *    => translate username to cn
    */
    AppSettings_Controler settings = Startup.getAppSettingsControler();
    LDAP_Manager ldap_manager = new LDAP_Manager();
    LdapContext ctx = ldap_manager.getLdapConnection();
    String user_uid = "";

    SearchControls constraints = new SearchControls();
    constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
    String[] attrIDs = "cn";
    constraints.setReturningAttributes(attrIDs);
    NamingEnumeration answer = null;

    try
        answer = ctx.search(settings.get_ldap_search_dn_1(), settings.get_ldap_uname_field_name_1() + "=" + principal, constraints);
        if (answer.hasMore()) 
            Attributes attrs = ((SearchResult) answer.next()).getAttributes();
            user_uid = attrs.get("cn").toString().substring(attrIDs[0].length() + 2);
         else 
            throw new Exception("Invalid User");
        
     catch (Exception ex) 
        // Error Handling & Fallback to my second LDAP-Server
        try
            answer = ctx.search(settings.get_ldap_search_dn_2(), settings.get_ldap_uname_field_name_2() + "=" + principal, constraints);
            if (answer.hasMore()) 
                Attributes attrs = ((SearchResult) answer.next()).getAttributes();
                user_uid = attrs.get("cn").toString().substring(attrIDs[0].length() + 2);
             else 
                throw new Exception("Invalid User");
            
        catch (Exception exe) 
            // Error Handling
        
    
    try 
        ctx.close();
     catch (NamingException ex) 
        JndiLdapRealm_custom_error_logger.error("getUserDn(); Fehler beim schließen: ", ex);
    
    //############################################################################################

    //############################################################################################
    // Shiro Standard Code -> add principal to String
    //sb.append(principal);
    /*############################################################################################

    /*############################################################################################
    * ALTERED CODE
    * Instead -> Add user's cn to String   
    */
    sb.append(user_uid);
    //############################################################################################

    if (suffixLength > 0) 
        sb.append(suffix);
    
    return sb.toString();

此添加执行 LDAP 查询以根据提供的用户名获取用户 uid (cn)。这样,用户只需在我的应用程序中输入其 LDAP 用户名即可登录,并遵循上述工作流程。

如果您有任何建议或 cmets,请不要犹豫,将它们发布在这里 ;)

【讨论】:

以上是关于Apache Shiro LDAP 配置(两步验证)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用带有 LDAP 身份验证的 Apache Shiro 添加角色授权

Apache shiro:使用 ldap 进行用户身份验证,使用数据库获得角色/权限?

Apache Shiro - 用于身份验证和属性的 LDAP/用于授权的 Ini

Zeppelin LDAP 身份验证

简单两步快速实现shiro的配置和使用,包含登录验证角色验证权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)

用于保护 REST api 的 Apache Shiro