After backing up a bit I was able to find the solution, did not have anything to do with Spring Security. LDAP treats a unicodePwd modification with a REPLACE_ATTRIBUTE as an administrative reset - set password - which ignores group policy (although it still enforced password complexity).
LDAP treats a unicodePwd modification with a REMOVE_ATTRIBUTE and ADD_ATTRIBUTE as a user change - change password - and enforces policy, password history, etc.
One more gotcha - if using admin creds for the LdapContextSource, these will not work changing a user password. You must get a separate context with the user auth, otherwise you get an AttributeInUseException, NO_ATTRIBUTE_OR_VAL.
setPassword is straightforward:
Code:
// set new password attribute
Attribute passwordAttr = new BasicAttribute("unicodePwd", encodePassword(newPassword));
ModificationItem repPasswordAttr = new ModificationItem(DirContextOperations.REPLACE_ATTRIBUTE, passwordAttr);
// enable account if locked out
Attribute enableAttr = new BasicAttribute("userAccountControl", Integer.toString(544));
ModificationItem repEnableAttr = new ModificationItem(DirContextOperations.REPLACE_ATTRIBUTE, enableAttr);
ldapTemplate.modifyAttributes(person.getDn(), new ModificationItem[] { repPasswordAttr, repEnableAttr});
changePassword just needs the new context:
Code:
// get context for user from existing context, can't be admin, context has to have auth as changing user
DirContext ctx = ldapTemplate.getContextSource().getContext(person.getDnWithPath(), oldPassword);
// set old/new password attributes
ModificationItem[] mods = new ModificationItem[2];
mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("unicodePwd", encodePassword(oldPassword)));
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("unicodePwd", encodePassword(newPassword)));
// Perform the update
ctx.modifyAttributes(person.getDn(), mods);
ctx.close();
Hope that helps someone