跳到主要内容

先决条件:

1.Java应用所在的服务器开通到ad01.acme.com 636端口的网络策略

2.AD管理员通过AD证书服务配置了LDAPS,并侦听636端口

3.创建一个AD用户账号666,用于修改其他用户的密码

用法:

传入两个参数:1.用户名 2.新密码

java -jar modifyADpassword.jar zhangsan NewPa4s#8281

开启SSL调试,诊断潜在的证书问题

java -Djavax.net.debug=ssl:handshake:verbose -jar modifyADpassword.jar

modifyADpassword.jar源码:

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.cert.X509Certificate;
import java.util.Hashtable;

public class ModifyAdPassword {

    public static void main(String[] args) {
        // --- 检查命令行参数 ---
        if (args.length != 2) {
            System.out.println("Usage: java ModifyAdPassword <employeeId> <newPassword>");
            System.out.println("Example: java ModifyAdPassword 888 MyNewP@ssw0rd!");
            return;
        }
        String employeeId = args[0];
        String newPassword = args[1];
        
        System.out.println("Searching for user with employeeId (sAMAccountName): " + employeeId);

        // --- 1. 配置连接信息 ---
        String ldapHost = "ad01.acme.com"; // 您的AD服务器地址
        String adminUser = "666@acme.com"; // 用于登录修改密码的账户 (UPN格式)
        String adminPassword = "YOUR_666_PASSWORD"; // 【必须替换】666账户的密码

        // --- 2. 设置JNDI环境参数 ---
        Hashtable<String, Object> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldaps://" + ldapHost + ":636");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, adminUser);
        env.put(Context.SECURITY_CREDENTIALS, adminPassword);

        // --- 3. 【核心】为JNDI连接指定一个信任所有证书的SSLSocketFactory ---
        // 这解决了 "unable to verify the first certificate" 的问题
        try {
            // 告诉JNDI使用我们自定义的Socket工厂类
            env.put("java.naming.ldap.factory.socket", TrustAllSSLSocketFactory.class.getName());
        } catch (Exception e) {
            System.err.println("Failed to set up custom SSL Socket Factory.");
            e.printStackTrace();
            return;
        }

        DirContext ctx = null;
        try {
            // --- 4. 连接到AD服务器 ---
            System.out.println("Connecting to AD server...");
            ctx = new InitialDirContext(env);
            System.out.println("Connection successful.");

            // --- 5. 搜索用户DN ---
            String userToModifyDN = findUserDNBySamAccountName(ctx, employeeId);

            if (userToModifyDN == null) {
                System.err.println("Error: User with sAMAccountName '" + employeeId + "' not found.");
                return;
            }
            System.out.println("Found user DN: " + userToModifyDN);

            // --- 6. 准备并执行密码修改 ---
            // AD要求密码必须是UTF-16LE编码,并且用双引号括起来
            byte[] newQuotedPassword;
            try {
                // 【修复】使用 try-catch 块处理 UnsupportedEncodingException
                newQuotedPassword = ("\"" + newPassword + "\"").getBytes("UTF-16LE");
            } catch (UnsupportedEncodingException e) {
                System.err.println("FATAL ERROR: The system does not support the required UTF-16LE encoding.");
                e.printStackTrace();
                return; // 如果编码不支持,程序无法继续,直接退出
            }
            
            Attribute passwordAttribute = new BasicAttribute("unicodePwd", newQuotedPassword);
            ModificationItem[] mods = new ModificationItem[1];
            mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, passwordAttribute);

            System.out.println("Modifying password...");
            ctx.modifyAttributes(userToModifyDN, mods);
            System.out.println("Password for user '" + employeeId + "' was changed successfully!");

        } catch (NamingException e) {
            System.err.println("An error occurred during LDAP operation.");
            e.printStackTrace();
            // 常见错误:
            // - 50: 权限不足 (666账户没有权限修改密码)
            // - 19: 约束冲突 (新密码不符合域的密码策略)
            // - 32: 没有此对象 (userToModifyDN 找不到)
        } finally {
            // --- 7. 关闭连接 ---
            if (ctx != null) {
                try {
                    ctx.close();
                    System.out.println("Connection closed.");
                } catch (NamingException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 根据sAMAccountName查找用户的完整Distinguished Name (DN)。
     */
    private static String findUserDNBySamAccountName(DirContext ctx, String samAccountName) throws NamingException {
        String searchBase = "DC=acme,DC=com"; // 搜索的根路径,通常为域的DN
        String searchFilter = String.format("(sAMAccountName=%s)", samAccountName);

        SearchControls controls = new SearchControls();
        controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        controls.setReturningAttributes(new String[0]); // 只需要DN,不需要其他属性

        NamingEnumeration<SearchResult> results = ctx.search(searchBase, searchFilter, controls);
        try {
            if (results.hasMore()) {
                SearchResult result = results.next();
                return result.getNameInNamespace();
            }
        } finally {
            results.close();
        }
        return null;
    }

    /**
     * 一个自定义的SSLSocketFactory,它会创建一个信任所有服务器证书的SSL Socket。
     * 这是为了解决LDAPS连接中因证书不被信任而导致的连接失败问题。
     * 注意:此类仅用于测试环境,因为它会禁用SSL/TLS的安全检查。
     */
    static class TrustAllSSLSocketFactory extends SSLSocketFactory {
        private final SSLSocketFactory delegate;

        public TrustAllSSLSocketFactory() throws Exception {
            // 创建一个信任所有证书的SSLContext
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new TrustManager[]{new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }}, new java.security.SecureRandom());
            delegate = context.getSocketFactory();
        }

        // 以下方法都是委托给内部的SSLSocketFactory
        @Override
        public String[] getDefaultCipherSuites() { return delegate.getDefaultCipherSuites(); }

        @Override
        public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); }

        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            return delegate.createSocket(s, host, port, autoClose);
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            return delegate.createSocket(host, port);
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
            return delegate.createSocket(host, port, localHost, localPort);
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            return delegate.createSocket(host, port);
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            return delegate.createSocket(address, port, localAddress, localPort);
        }
    }
}


留下回复