public class LdapAuthorizingRealm extendsJndiLdapRealm {private static final Logger logger = LoggerFactory.getLogger(LdapAuthorizingRealm.class);privateString rootDN;publicString getRootDN() {returnrootDN;
}public voidsetRootDN(String rootDN) {this.rootDN =rootDN;
}/*** 登录时调用
*
*@paramtoken
*@return*@throwsAuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throwsAuthenticationException {
AuthenticationInfo info;try{
info=queryForAuthenticationInfo(token, getContextFactory());
getAuthorizationInfo(((UsernamePasswordToken) token).getUsername());
}catch(AuthenticationNotSupportedException e) {
String msg= "Unsupported configured authentication mechanism";throw newUnsupportedAuthenticationMechanismException(msg, e);
}catch(javax.naming.AuthenticationException e) {
String msg= "LDAP authentication failed.";throw newAuthenticationException(msg, e);
}catch(NamingException e) {
String msg= "LDAP naming error while attempting to authenticate user.";throw newAuthenticationException(msg, e);
}catch(UnknownAccountException e) {
String msg= "账号不存在!";throw newUnknownAccountException(msg, e);
}catch(IncorrectCredentialsException e) {
String msg= "IncorrectCredentialsException";throw newIncorrectCredentialsException(msg, e);
}returninfo;
}/*** 授权
*
*@paramprincipalCollection
*@return
*/@OverrideprotectedAuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()//(可能是关闭浏览器,或超时),但此时缓存依旧存在(principals),所以会自己跑到授权方法里。
if (!SecurityUtils.getSubject().isAuthenticated()) {
doClearCache(principalCollection);
SecurityUtils.getSubject().logout();return null;
}//获取当前登录的用户名
String username =(String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo= newSimpleAuthorizationInfo();
Session session=SecurityUtils.getSubject().getSession();
authorizationInfo.setStringPermissions((Set) session.getAttribute("permissions"));returnauthorizationInfo;
}/*** 连接LDAP查询用户信息是否存在
*
* 1. 从页面得到登陆名和密码。注意这里的登陆名和密码一开始并没有被用到。
* 2. 先匿名绑定到LDAP服务器,如果LDAP服务器没有启用匿名绑定,一般会提供一个默认的用户,用这个用户进行绑定即可。
* 3. 之前输入的登陆名在这里就有用了,当上一步绑定成功以后,需要执行一个搜索,而filter就是用登陆名来构造,形如: "CN=*(xn607659)" 。
* 搜索执行完毕后,需要对结果进行判断,如果只返回一个entry,这个就是包含了该用户信息的entry,可以得到该 entry的DN,后面使用。
* 如果返回不止一个或者没有返回,说明用户名输入有误,应该退出验证并返回错误信息。
* 4. 如果能进行到这一步,说明用相应的用户,而上一步执行时得到了用户信息所在的entry的DN,这里就需要用这个DN和第一步中得到的password重新绑定LDAP服务器。
* 5. 执行完上一步,验证的主要过程就结束了,如果能成功绑定,那么就说明验证成功,如果不行,则应该返回密码错误的信息。
* 这5大步就是基于LDAP的一个 “两次绑定” 验证方法
*
*@paramtoken
*@paramldapContextFactory
*@return*@throwsNamingException*/@OverrideprotectedAuthenticationInfo queryForAuthenticationInfo(
AuthenticationToken token, LdapContextFactory ldapContextFactory)throwsNamingException {
Object principal= token.getPrincipal();//输入的用户名
Object credentials = token.getCredentials();//输入的密码
String userName =principal.toString();
String password= new String((char[]) credentials);
LdapContext systemCtx= null;
LdapContext ctx= null;try{//使用系统配置的用户连接LDAP
systemCtx =ldapContextFactory.getSystemLdapContext();
SearchControls constraints= newSearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);//搜索范围是包括子树//String returnedAtts[] = { "uid","displayName","cn","company","department","mailNickname"};//constraints.setReturningAttributes(returnedAtts);
NamingEnumeration results = systemCtx.search(rootDN, "UID=" +principal , constraints);if (results != null && !results.hasMore()) {throw newUnknownAccountException();
}else{while(results.hasMore()) {
SearchResult si=(SearchResult) results.next();
principal= si.getName() + "," +rootDN;
logger.debug(si.getAttributes().get("company").toString());
logger.debug(si.getAttributes().get("department").toString());
}
logger.info("DN=[" + principal + "]");try{//根据查询到的用户与输入的密码连接LDAP,用户密码正确才能连接
ctx =ldapContextFactory.getLdapContext(principal, credentials);
dealUser(userName, password);
}catch(NamingException e) {throw newIncorrectCredentialsException();
}return new SimpleAuthenticationInfo(userName, MD5Util.MD5(userName +password).toLowerCase(), getName());
}
}finally{//关闭连接
LdapUtils.closeContext(systemCtx);
LdapUtils.closeContext(ctx);
}
}/*** 将LDAP查询到的用户保存到sys_user表
*
*@paramuserName*/
private voiddealUser(String userName, String password) {if(StringUtil.isEmpty(userName)) {return;
}//TO DO...
}/*** 获取权限码
*
*@paramusername
*@return
*/
private Map>getAuthorizationInfo(String username) {
Map> authorizationMap = new HashMap>();
Set codeSet = new HashSet();
Session session=SecurityUtils.getSubject().getSession();
//查询数据库的用户权限
//......authorizationMap.put("permissions", codeSet);
session.setAttribute("permissions", codeSet);
logger.debug("当前登录账户:{}的权限集合:{}", username, codeSet);returnauthorizationMap;
}/*** 设定Password校验的Hash算法与迭代次数.这里使用了自定义的加密算法*/@PostConstructpublic voidinitCredentialsMatcher() {
HashedCredentialsMatcher matcher= new HashedCredentialsMatcher("MD5") {
@Overridepublic booleandoCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object credentials=token.getCredentials();
UsernamePasswordToken token1=(UsernamePasswordToken) token;if (credentials == null) {
String msg= "Argument for byte conversion cannot be null.";throw newIllegalArgumentException(msg);
}byte[] bytes = null;if (credentials instanceof char[]) {
bytes= CodecSupport.toBytes(new String((char[]) credentials), PREFERRED_ENCODING);
}else{
bytes=objectToBytes(credentials);
}
String tokenHashedCredentials= MD5Util.MD5(token1.getUsername() + newString(bytes)).toLowerCase();
String accountCredentials=getCredentials(info).toString();returntokenHashedCredentials.equals(accountCredentials);
}
};
matcher.setHashIterations(1);
setCredentialsMatcher(matcher);
}
}