如何使用给定的LdapContext检查ldap和java中的用户密码?

如何使用给定的LdapContext检查ldap和java中的用户密码?,java,authentication,web-applications,ldap,Java,Authentication,Web Applications,Ldap,我有一个web应用程序,用户必须登录。密码存储在LDAP服务器中。关于LDAP服务器的所有信息都作为外部jndi资源存储在应用程序服务器(glassfish)中。因此,我的应用程序对LDAP服务器一无所知,只获得如下LdapContext: @Resource(name = "ldap/users") private LdapContext ctx; Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY

我有一个web应用程序,用户必须登录。密码存储在LDAP服务器中。关于LDAP服务器的所有信息都作为外部jndi资源存储在应用程序服务器(glassfish)中。因此,我的应用程序对LDAP服务器一无所知,只获得如下LdapContext:

@Resource(name = "ldap/users")
private LdapContext ctx;
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

DirContext ctx = new InitialDirContext(env);
在这种情况下,很容易更改或读取为用户存储的信息,但如何检查他们的密码? 通常我只需要做一个新的连接来检查用户密码。像这样:

@Resource(name = "ldap/users")
private LdapContext ctx;
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

DirContext ctx = new InitialDirContext(env);
但是因为我不知道这个参数,所以我不能这样做。那么,如何使用LdapContext检查用户的密码是否正确? 密码是加密存储的(ssha),所以我不能只比较属性

谢谢
Raffael

您应该能够从ldap上下文获取环境,克隆它,然后为要检查的用户放置主体和凭据:

@Resource(name = "ldap/users")
private LdapContext ldapContext;

Hashtable environment = ldapContext.getEnvironment().clone();
environment.put(Context.SECURITY_PRINCIPAL, userDN);
environment.put(Context.SECURITY_CREDENTIALS, userPassword);

DirContext dirContext = new InitialDirContext(environment);

这是一种解决方案,可用于使用DN以外的其他内容对用户进行身份验证,例如使用
uid
sAMAccountName

具体步骤如下:

  • 连接到LDAP服务器
  • 向我们知道其DN和凭据的服务用户进行身份验证
  • 搜索要进行身份验证的用户,使用某些属性搜索他(例如
    sAMAccountName
  • 获取我们找到的用户的DN
  • 使用找到的DN和密码打开到LDAP服务器的另一个连接
  • 如果找到了用户并且身份验证工作正常,那么您就可以了
  • 代码示例:

    public static boolean performAuthentication() {
    
        // service user
        String serviceUserDN = "cn=Mister Service,ou=Users,dc=example,dc=com";
        String serviceUserPassword = "abc123#!$";
    
        // user to authenticate
        String identifyingAttribute = "uid";
        String identifier = "maxdev";
        String password = "jkl987.,-";
        String base = "ou=Users,dc=example,dc=com";
    
        // LDAP connection info
        String ldap = "localhost";
        int port = 10389;
        String ldapUrl = "ldap://" + ldap + ":" + port;
    
        // first create the service context
        DirContext serviceCtx = null;
        try {
            // use the service user to authenticate
            Properties serviceEnv = new Properties();
            serviceEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            serviceEnv.put(Context.PROVIDER_URL, ldapUrl);
            serviceEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
            serviceEnv.put(Context.SECURITY_PRINCIPAL, serviceUserDN);
            serviceEnv.put(Context.SECURITY_CREDENTIALS, serviceUserPassword);
            serviceCtx = new InitialDirContext(serviceEnv);
    
            // we don't need all attributes, just let it get the identifying one
            String[] attributeFilter = { identifyingAttribute };
            SearchControls sc = new SearchControls();
            sc.setReturningAttributes(attributeFilter);
            sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
    
            // use a search filter to find only the user we want to authenticate
            String searchFilter = "(" + identifyingAttribute + "=" + identifier + ")";
            NamingEnumeration<SearchResult> results = serviceCtx.search(base, searchFilter, sc);
    
            if (results.hasMore()) {
                // get the users DN (distinguishedName) from the result
                SearchResult result = results.next();
                String distinguishedName = result.getNameInNamespace();
    
                // attempt another authentication, now with the user
                Properties authEnv = new Properties();
                authEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                authEnv.put(Context.PROVIDER_URL, ldapUrl);
                authEnv.put(Context.SECURITY_PRINCIPAL, distinguishedName);
                authEnv.put(Context.SECURITY_CREDENTIALS, password);
                new InitialDirContext(authEnv);
    
                System.out.println("Authentication successful");
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (serviceCtx != null) {
                try {
                    serviceCtx.close();
                } catch (NamingException e) {
                    e.printStackTrace();
                }
            }
        }
        System.err.println("Authentication failed");
        return false;
    }
    
    公共静态布尔性能验证(){
    //服务用户
    String serviceUserDN=“cn=Mister Service,ou=Users,dc=example,dc=com”;
    字符串serviceUserPassword=“abc123#!$”;
    //要验证的用户
    标识attribute=“uid”的字符串;
    字符串标识符=“maxdev”;
    字符串password=“jkl987.,-”;
    String base=“ou=Users,dc=example,dc=com”;
    //LDAP连接信息
    字符串ldap=“localhost”;
    int端口=10389;
    字符串ldapUrl=“ldap://”+ldap+:“+port;
    //首先创建服务上下文
    DirContext serviceCtx=null;
    试一试{
    //使用服务用户进行身份验证
    Properties serviceEnv=新属性();
    serviceev.put(Context.INITIAL_Context_工厂,“com.sun.jndi.ldap.LdapCtxFactory”);
    servicenev.put(Context.PROVIDER\u URL,ldapUrl);
    serviceEnv.put(Context.SECURITY_身份验证,“简单”);
    serviceEnv.put(Context.SECURITY\u主体,serviceUserDN);
    serviceEnv.put(Context.SECURITY\u凭证、serviceUserPassword);
    serviceCtx=新的InitialDirContext(serviceEnv);
    //我们不需要所有的属性,只要让它得到一个可识别的属性就行了
    字符串[]attributeFilter={IdentificationAttribute};
    SearchControls sc=新的SearchControls();
    sc.设置返回属性(属性过滤器);
    sc.setSearchScope(SearchControls.SUBTREE_范围);
    //使用搜索筛选器仅查找我们要验证的用户
    字符串searchFilter=“(“+IdentificationAttribute+”=“+identifier+”)”;
    NamingEnumeration results=serviceCtx.search(base、searchFilter、sc);
    if(results.hasMore()){
    //从结果中获取用户DN(DifferentiedName)
    SearchResult=results.next();
    字符串differentiedName=result.getNameInNamespace();
    //尝试另一次身份验证,现在与用户进行身份验证
    Properties authEnv=新属性();
    authEnv.put(Context.INITIAL_Context_工厂,“com.sun.jndi.ldap.LdapCtxFactory”);
    authEnv.put(Context.PROVIDER\u URL,ldapUrl);
    authEnv.put(Context.SECURITY\u PRINCIPAL,distributedName);
    authEnv.put(Context.SECURITY\u凭证、密码);
    新的InitialDirContext(authEnv);
    System.out.println(“身份验证成功”);
    返回true;
    }
    }捕获(例外e){
    e、 printStackTrace();
    }最后{
    如果(serviceCtx!=null){
    试一试{
    serviceCtx.close();
    }捕获(NamingE例外){
    e、 printStackTrace();
    }
    }
    }
    System.err.println(“身份验证失败”);
    返回false;
    }
    
    我在我的应用程序中也做了同样的事情。 以下是可能对您有用的示例

        package com.agileinfotech.bsviewer.servlet;
    
        import java.io.IOException;
        import javax.servlet.RequestDispatcher;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import javax.naming.*;
        import javax.naming.directory.*;
        import java.util.Hashtable;
    
        public class Login extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
    
        public Login() {
        super();
        }
    
        protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        final String SUCCESS = "loin.jsp";
        final String FAILURE = "Failure.html";
        String strUrl = "login.html";
        String username = request.getParameter("username");
        String password = request.getParameter("password");
    
    
    
        Hashtable env = new Hashtable(11);
    
        boolean b = false;
    
        env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:10389");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "uid="+ username +",ou=system");
        env.put(Context.SECURITY_CREDENTIALS, password);
    
        try {
        // Create initial context
        DirContext ctx = new InitialDirContext(env);
    
        // Close the context when we're done
        b = true;
        ctx.close();
    
        } catch (NamingException e) {
        b = false;
        }finally{
        if(b){
        System.out.print("Success");
        strUrl = SUCCESS;
        }else{
        System.out.print("Failure");
        strUrl = FAILURE;
        }
        }
        RequestDispatcher rd = request.getRequestDispatcher(strUrl);
        rd.forward(request, response);
    
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request,response);
        }
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request,response);
        } 
        }
    

    在真实的应用程序LDAP服务器中,密码以哈希代码形式存储,每当任何access manager从用户处获取密码时,该纯文本密码将再次使用相同的密钥进行哈希,并检查为LDAP中存储的密码。因此,您无法从LDAP服务器获取普通密码。
    因此,如果您知道密钥,只有这样您才能解密。

    你好,尼古拉,我也认为这是首选方法,因为您可以根据不同的标准正确搜索用户。但是我尝试了这个方法,检索
    discrimitedName
    属性总是返回
    null
    。我认为使用
    result.getNameInNamespace()
    是一个更好的选择,您认为如何?您好,Max,感谢您完善我的解决方案。关于获取DN,我相信这可能取决于LDAP服务提供商。在我5年前的例子中,在经典的Microsoft AD LDAP上,它通过提取属性
    attrs.get(“DifferentizedName”)
    工作。您无法解密密码散列,并且没有“密钥”。