Because the company has many internal systems and many account systems, it is necessary to realize unified account account login to different systems. LDAP is now used to manage account numbers.
Shiro also provides LDAP support, combined with online information, write the following content.
- Jeesite The existing account system. Users to maintain the email address and correspond to the email address stored in LDAP
- Custom LDAPAUTHORIZINGREALM inherit the JNDILDAPREALM class, rewriting the method inside.
- Add custom Realm in Spring-Context-SHIRO.XML
package com.xxx.modules.sys.security;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.AuthenticationNotSupportedException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.ldap.UnsupportedAuthenticationMechanismException;
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* LdapRealm
* @author huangkai
*
*/
public class LdapAuthorizingRealm extends JndiLdapRealm{
private Logger logger = LoggerFactory.getLogger(getClass());
private SystemService systemService;
private String rootDN;
public String getRootDN() {
return rootDN;
}
public void setRootDN(String rootDN) {
this.rootDN = rootDN;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken){
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
// check login verification code
if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){
Session session = UserUtils.getSession();
String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE);
if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){
throw new AuthenticationException("msg: verification code error, please try it." ");
}
}
AuthenticationInfo info;
try {
info = queryForAuthenticationInfo(token, getContextFactory());
//getAuthorizationInfo(token.getUsername());
} catch (AuthenticationNotSupportedException e) {
String msg = "msg:Unsupported configured authentication mechanism";
throw new UnsupportedAuthenticationMechanismException(msg, e);
} catch (javax.naming.AuthenticationException e) {
String msg = "msg:LDAP authentication failed.";
throw new AuthenticationException(msg, e);
} catch (NamingException e) {
String msg = "msg:LDAP naming error while attempting to authenticate user.";
throw new AuthenticationException(msg, e);
} catch (UnknownAccountException e) {
String msg = "MSG: The account does not exist!";
throw new UnknownAccountException(msg, e);
} catch (IncorrectCredentialsException e) {
String msg = "msg: password error";
throw new IncorrectCredentialsException(msg, e);
}
return info;
}
/**
* Connect LDAP to query whether the user information exists
* <p>
* 1. Get the login name and password from the page. Note that the login name and password here were not used at the beginning.
* 2. First anonymous binding to the LDAP server. If the LDAP server does not enable anonymous binding, it will generally provide a default user. Use this user for binding.
* 3. The login name entered before is useful here. When the previous step is successful, a search needs to be performed, and the Filter is constructed with the login name, such as: "cn =* (xn607659)".
* After the search is executed, you need to judge the results. If you only return one Entry, this is the Entry containing the user information, you can get the DN of the Entry, and use it later.
* If there are more than one or no return, it means that the user name input is wrong, and you should quit verification and return the error message.
* 4. If this step can be performed, it means that the corresponding users are used, and the DN of the entry information where the user information is located in the previous step. Essence
* 5. After the previous step is performed, the main process of verification is over. If it can be successfully bound, it means that the verification is successful. If it does not work, the password error information should be returned.
* These 5 big steps are a "two -binding" verification method based on LDAP
*
* @param token
* @param ldapcontextFactory
* @Return
* @throws namingexception
*/
@Override
protected AuthenticationInfo queryForAuthenticationInfo(
AuthenticationToken authcToken, LdapContextFactory ldapContextFactory)
throws NamingException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
Object principal = token.getPrincipal();// The user name entered
Object credentials = token.getCredentials();// The password input
String userName = principal.toString();
String password = new String((char[]) credentials);
LdapContext systemCtx = null;
LdapContext ctx = null;
try {
// Use the system configured user to connect LDAP
systemCtx = ldapContextFactory.getSystemLdapContext();
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);// The search range includes subtree
NamingEnumeration<SearchResult> results = systemCtx.search(rootDN, "cn=" + principal, constraints);
if (results != null && !results.hasMore()) {
throw new UnknownAccountException();
} else {
String mail=null;
while (results.hasMore()) {
SearchResult si = (SearchResult) results.next();
principal = si.getName() + "," + rootDN;
mail= si.getAttributes().get("mail").get(0).toString();
logger.debug(si.getAttributes().get("mail").toString());
}
logger.info("DN=[" + principal + "]");
try {
// Connect LDAP according to the inquiries with the input password, the user password is correct to connect
ctx = ldapContextFactory.getLdapContext(principal, credentials);
dealUser(userName, password);
} catch (NamingException e) {
throw new IncorrectCredentialsException();
}
// Check the user name password
if(StringUtils.isNotBlank(mail)){
User user = getSystemService().getUserByMail(mail);
if (user != null) {
if (Global.NO.equals(user.getLoginFlag())){
throw new AuthenticationException("MSG: The account is prohibited to log in.");
}
byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()),
user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
} else {
throw new AuthenticationException("msg:"+mail+"No account corresponding to the mailbox");
}
}else{
throw new AuthenticationException("msg:"+"LDAP does not configure the user's mailbox");
}
}
} finally {
// Turn off the connection
twenty two# /**
* Authorized query recovery function, calling for authentication, but calling when there is no user authorized information in the cache
*/
LdapUtils.closeContext(systemCtx);
LdapUtils.closeContext(ctx);
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Principal principal = (Principal) getAvailablePrincipal(principals);
// Get the user currently logged in
if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){
Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession());
if (sessions.size() > 0){
// If you log in and come in, kick out the online user
if (UserUtils.getSubject().isAuthenticated()){
for (Session session : sessions){
getSystemService().getSessionDao().delete(session);
}
}
// Remember what I came in, and the current user has logged in, then exit the current user prompt message.
else{
UserUtils.getSubject().logout();
throw new AuthenticationException("MSG: The account has been logged in other places, please log in again.");
}
}
}
User user = getSystemService().getUserByLoginName(principal.getLoginName());
if (user != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List<Menu> list = UserUtils.getMenuList();
for (Menu menu : list){
if (StringUtils.isNotBlank(menu.getPermission())){
// Add permissions based on Permit
for (String permission : StringUtils.split(menu.getPermission(),",")){
info.addStringPermission(permission);
}
}
}
// Add user permissions
info.addStringPermission("user");
// Add user role information
for (Role role : user.getRoleList()){
info.addRole(role.getEnname());
}
// Update login IP and time
getSystemService().updateUserLoginInfo(user);
// Record login log
LogUtils.saveLog(Servlets.getRequest(), "System login");
return info;
} else {
return null;
}
}
/**
* Save the user found by LDAP to the sys_user table
*
* @param username
*/
private void dealUser(String userName, String password) {
if (StringUtils.isEmpty(userName)) {
return;
}
//TO DO...
}
/**
* Obtaining permissions code
*
* @param username
* @Return
*/
private Map<String, Set<String>> getAuthorizationInfo(String username) {
Map<String, Set<String>> authorizationMap = new HashMap<String, Set<String>>();
Set<String> codeSet = new HashSet<String>();
Session session = SecurityUtils.getSubject().getSession();
// Query user permissions of the database
//......
authorizationMap.put("permissions", codeSet);
session.setAttribute("permissions", codeSet);
logger.debug("The current login account: {} permissions collection: {}", username, codeSet);
return authorizationMap;
}
/**
* Get the system business object
*/
public SystemService getSystemService() {
if (systemService == null){
systemService = SpringContextHolder.getBean(SystemService.class);
}
return systemService;
}
}
<!-Define SHIRO safety management configuration->
<bean id = "securityManager" class = "org.apache.shiro.web.mgt.defaultWebSecurityManager">
<!-<Property name = "Realm" Ref = "SystemAutHorizingRealm" />->
<property name = "sessionManager" ref = "sessionManager" /> />
<propoperty name = "cacheManager" ref = "shirocacheManager" /> />
<property name = "authenticator" ref = "authenticator"/>
<property name = "realms">
<list>
<ref bean = "systemmauthorizingrealm" />
<ref bean = "ldapauthorizingRealm" />
</list>
</Property>
</bean>
<bean id = "authenticator" class = "org.apache.shiro.authc.pam.modularRealRealMauthenticator">
<property name = "authenticationStrategy">
<bean class = "org.apache.shiro.authc.pam.firtSuccessfulstrategy"> </Bean>
</Property>
</bean>
<!-Rewilling LDAP certification here ROOTDN is the root directory of the employee of the search company->
<bean id = "ldapaultHorizingRealm" class = "com.xxx.modules.Sys.SecurgautHorizingrealm">
<propoperty name = "rootdn" value = "ou = ****, dc = ****, dc = com"/>
<property name = "userdntemplate" value = "{0}"/>
<propoperty name = "contextFactory" ref = "contextFactory"/>/>
</bean>
<!-Configure the LDAP path and configure a default user and password->
<bean id = "contextFactory" class = "org.apache.shiro.realm.ldap.jndildapcontextFactory">>
<propoperty name = "url" value = "ldap: //***.***.****/"/>
<propoperty name = "SystemUsername" Value = "CN = *****, OU = Operating Platform Product Line, OU = Product R & D Center, OU = ****, DC = ****, DC = Com"/ "
<propoperty name = "SystemPassword" value = "******"/>
</bean>
Shiro certification strategy, if there are multiple Realm, how can it be regarded as a successful certification, which requires the certification strategy.
AuthenticationStrategy interface default implementation:
- firstSuccessFulstrategy: As long as a realm verification is successful, only the first Realm authentication authentication information is returned. Other ignition;
- AtleastoneSuccessfulStrategy: As long as there is a Realm verification success, unlike FirstSuccessFulstrategy, it will return to all Realm authentication certification information;
- AllSuccessFulstrategy: All Realm verification is successful, and the authentication information of successful authentication is returned. If there is a failure, it will fail.
- ModularrealMauthenticator default is the AsleastoneSuccessfulstrategy strategy