记一次java实现修改windows AD域的密码

根据windows帮助文档说明,修改ad域密码是需要使用ssl认证方式。https://support.microsoft.com/zh-cn/help/269190/how-to-change-a-windows-active-directory-and-lds-user-password-through

在实现的过程中,先通过将证书到java证书库,然后使用ssl认证方式修改密码报异常,最后才选择修改socket连接信任所有证书的形式。

导入证书到java证书库所借鉴文章地址:https://blog.csdn.net/hc1017/article/details/81293323

修改socket连接信任所有证书借鉴文章地址:https://my.oschina.net/qiaojj/blog/2251630

一、主要代码

1.自定义socket连接信任所有证书类

package main.util;
 
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
 
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
 
/**
 * 自定义 SSLSocketFactory
 */
public class DummySSLSocketFactory extends SSLSocketFactory {
 
	private SSLSocketFactory factory;
	
	public DummySSLSocketFactory() {
	    
        try {
        
        SSLContext sslcontext = SSLContext.getInstance("TLS");
        
        sslcontext.init( null, // No KeyManager required
        
        new TrustManager[] { new DummyTrustManager()},
        
        new java.security.SecureRandom());
        
        factory = ( SSLSocketFactory) sslcontext.getSocketFactory();
        
        } catch( Exception ex) { ex.printStackTrace(); }
    
    }
	
	public static SocketFactory getDefault() {
	    
	    return new DummySSLSocketFactory();
	    
	}
	
	@Override
	public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3)
			throws IOException {
		// TODO Auto-generated method stub
		return factory.createSocket( arg0, arg1, arg2, arg3);
	}
	
 
	@Override
	public String[] getDefaultCipherSuites() {
		// TODO Auto-generated method stub
		return factory.getSupportedCipherSuites();
	}
 
	@Override
	public String[] getSupportedCipherSuites() {
		// TODO Auto-generated method stub
		return factory.getSupportedCipherSuites();
	}
 
	@Override
	public Socket createSocket(String arg0, int arg1) throws IOException,
			UnknownHostException {
		// TODO Auto-generated method stub
		return factory.createSocket( arg0, arg1);
	}
 
	@Override
	public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
		// TODO Auto-generated method stub
		return factory.createSocket( arg0, arg1);
	}
 
	@Override
	public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
			throws IOException, UnknownHostException {
		// TODO Auto-generated method stub
		return factory.createSocket( arg0, arg1, arg2, arg3);
	}
 
	@Override
	public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2,
			int arg3) throws IOException {
		// TODO Auto-generated method stub
		return factory.createSocket( arg0, arg1, arg2, arg3);
	}
 
}
package main.util;
 
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
 
import javax.net.ssl.X509TrustManager;
 
/**
 * 自定义信任证书管理器
 */
public class DummyTrustManager implements X509TrustManager {
 
	@Override
	public void checkClientTrusted(X509Certificate[] arg0, String arg1)
			throws CertificateException {
		// TODO Auto-generated method stub
		return;
	}
 
	@Override
	public void checkServerTrusted(X509Certificate[] arg0, String arg1)
			throws CertificateException {
		// TODO Auto-generated method stub
		return;
	}
 
	@Override
	public X509Certificate[] getAcceptedIssuers() {
		// TODO Auto-generated method stub
		return new X509Certificate[0];
	}
 
}

2. 初始化ldap上下文

以ssl认证方式初始化

public static InitialLdapContext sslInit(String userDN) throws NamingException{
		
		Hashtable<String, String> environment = new Hashtable<String, String>(7);
		environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		// ldap 服务器地址+dn
		environment.put(Context.PROVIDER_URL, "ldaps://10.2.108.121:636/" + userDN);
		// 登录方式
		environment.put(Context.AUTHORITATIVE, "true"); 
		environment.put(Context.SECURITY_AUTHENTICATION, "simple");
		// 登录账号
		environment.put(Context.SECURITY_PRINCIPAL, LDAPConstants.SECURITY_PRINCIPAL);
		// 登录密码
		environment.put(Context.SECURITY_CREDENTIALS, LDAPConstants.SECURITY_CREDENTIALS);
		// 认证协议
		environment.put(Context.SECURITY_PROTOCOL, "ssl");
	
		// 指定java.naming.ldap.factory.socket
		environment.put(LDAPConstants.LDAP_FACTORY_SOCKET, "main.util.DummySSLSocketFactory");
		return new InitialLdapContext(environment, null);
	}

普通初始化

public static InitialLdapContext init() throws NamingException{
		Hashtable<String, String> environment = new Hashtable<String, String>(5);
		environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		// ldap 服务器地址+dn
		environment.put(Context.PROVIDER_URL, "ldap://10.2.108.121/DC=coffee,DC=com");
		// 登录方式
		environment.put(Context.AUTHORITATIVE, "simple");
		// 登录账号
		environment.put(Context.SECURITY_PRINCIPAL, LDAPConstants.SECURITY_PRINCIPAL);
		// 登录密码
		environment.put(Context.SECURITY_CREDENTIALS, LDAPConstants.SECURITY_CREDENTIALS);
		return new InitialLdapContext(environment, null);
	}

3. 获取需修改密码的dn路径

private String searchLDAP(InitialLdapContext initialLadpContext,
			String cnString) throws NamingException {
		SearchControls constraints = new SearchControls();
		constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
		constraints.setReturningObjFlag(true);
		NamingEnumeration<SearchResult> namingEnumeration = null;
		Pattern pattern = Pattern.compile(LDAPConstants.DESCRIPTION_FORMAT_STRING);
		String searchString;
		if (cnString.indexOf("@") > 0) {
			searchString = "mail=".concat(cnString);
			
		} else if (pattern.matcher(cnString).matches()) {
			searchString = "Description=".concat(cnString);
		} else {
			searchString = "sAMAccountName=".concat(cnString);
		}
		namingEnumeration = initialLadpContext.search(LDAPConstants.EMPTY_STRING,
				searchString, constraints);
		while (namingEnumeration.hasMore()) {
			Object obj = namingEnumeration.nextElement();
			if (null != obj && obj instanceof SearchResult) {
				SearchResult searchResult = (SearchResult) obj;
                                String userDN = searchResult.getName();
				return userDN;
			}
//namingEnumeration.hasMore() 判断到最后一个元素之后再执行则会报异常,所以在确认查询值唯一的情况下使用break语句进行跳出循环
			break;
		}
		return null;
	}

4.修改密码

public static void main(String[] args) {
		// TODO Auto-generated method stub
		if (args.length != 2) {
			System.out.print("exit");
			System.exit(-1);
		}
		String identifyString = args[0] ;
		String newPassword = args[1];
		if (identifyString.isEmpty() || identifyString.isEmpty()) {
			System.out.print("修改密码失败,身份信息及新密码不能为空");
		}
 
		InitialLdapContext initialLdapContext;
		try {
			initialLdapContext = InitLDAPContext.init();
			String searchNameString = searchLDAP(initialLdapContext, identifyString);
			if (null != searchNameString) {
				StringBuilder stringBuilder = new StringBuilder(searchNameString);
				String userDN = stringBuilder.append(",DC=coffee,DC=com").toString();
				stringBuilder = new StringBuilder();
				stringBuilder.append("\"").append(newPassword).append("\"");
				String newQuotedPassword = stringBuilder.toString();
				byte[] newUnicodePassword;
				newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
				ModificationItem[] mods = new ModificationItem[1];
				mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
						new BasicAttribute("unicodePwd", newUnicodePassword));
				initialLdapContext = InitLDAPContext.sslInit(userDN);
				initialLdapContext.modifyAttributes(LDAPConstants.URL.concat(userDN), mods);
			}
		} catch (NamingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.print(String.format("%s 未找到该身份信息的目录", identifyString));
	}

二、遇到的问题

1. 对目录使用namingEnumeration.hasMore()判断是否还有值时,判断到最后一个值之后,即判断到没有值时会报异常。

解决方法,确认检索值唯一则使用break跳出循环,不再执行hasMore()方法进行判断。

2. 在使用initialLdapContext.modifyAttributes()方法执行替换方法如果只传入DN目录路径会报"NOT_OBJECT"异常。

解决方式发法,使用完整的ldap路径,即"ldap://10.2.108.121/cn=test1,ou=company,dc=coffee,dc=com"。

3. 代码部署在jdk1.7上可运行,在jdk1.8上运行会报javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException:No subject alter IP address 10.2.108.121 found.异常。

解决方法,jvm启动命令加上 -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true

三、其他借鉴文档

1.javax.naming.NameNotFoundException:[LDAP: error code 32 - 0000208D:……]异常解决:https://community.oracle.com/message/4698474

2.ldap错误备忘录:https://my.oschina.net/u/126681/blog/80774

3.Java命名目录接口(JNDI)教程--LDAP用户的高级主题:https://blog.csdn.net/feihoo88/article/details/45076059

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值