jeesite1.2.7 shiro如何集成LDAP实现多种校验共存

jeesite1.2.7 shiro如何集成LDAP实现多种校验共存

因为公司内部系统多,账号体系多,现需要实现统一账号账号登录不同系统,现采用LDAP来管理账号。
刚好shiro也提供LDAP的支持,结合网上资料写下如下内容。

总体方案

  1. jeesite现有账号体系用户自己维护email地址与LDAP中存储的email地址一一对应
  2. 自定义LdapAuthorizingRealm继承JndiLdapRealm类,重写里面方法。
  3. 在spring-context-shiro.xml添加自定义realm

代码实现

  1. 自定义LdapAuthorizingRealm

package com.sijibao.bams.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;

import com.sijibao.bams.common.config.Global;
import com.sijibao.bams.common.servlet.ValidateCodeServlet;
import com.sijibao.bams.common.utils.Encodes;
import com.sijibao.bams.common.utils.SpringContextHolder;
import com.sijibao.bams.common.web.Servlets;
import com.sijibao.bams.modules.sys.entity.Menu;
import com.sijibao.bams.modules.sys.entity.Role;
import com.sijibao.bams.modules.sys.entity.User;
import com.sijibao.bams.modules.sys.service.SystemService;
import com.sijibao.bams.modules.sys.utils.LogUtils;
import com.sijibao.bams.modules.sys.utils.UserUtils;
import com.sijibao.bams.modules.sys.web.LoginController;

/**
 * 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;
		
		// 校验登录验证码
		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:验证码错误, 请重试.");
			}
		}
		
		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:账号不存在!";
            throw new UnknownAccountException(msg, e);
        } catch (IncorrectCredentialsException e) {
            String msg = "msg:密码错误";
            throw new IncorrectCredentialsException(msg, e);
        }

        return info;
	}
    
	
	
	/**
     * 连接LDAP查询用户信息是否存在
     * <p>
     * 1. 从页面得到登陆名和密码。注意这里的登陆名和密码一开始并没有被用到。
     * 2. 先匿名绑定到LDAP服务器,如果LDAP服务器没有启用匿名绑定,一般会提供一个默认的用户,用这个用户进行绑定即可。
     * 3. 之前输入的登陆名在这里就有用了,当上一步绑定成功以后,需要执行一个搜索,而filter就是用登陆名来构造,形如: "CN=*(xn607659)" 。
     * 搜索执行完毕后,需要对结果进行判断,如果只返回一个entry,这个就是包含了该用户信息的entry,可以得到该 entry的DN,后面使用。
     * 如果返回不止一个或者没有返回,说明用户名输入有误,应该退出验证并返回错误信息。
     * 4. 如果能进行到这一步,说明用相应的用户,而上一步执行时得到了用户信息所在的entry的DN,这里就需要用这个DN和第一步中得到的password重新绑定LDAP服务器。
     * 5. 执行完上一步,验证的主要过程就结束了,如果能成功绑定,那么就说明验证成功,如果不行,则应该返回密码错误的信息。
     * 这5大步就是基于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();//输入的用户名
        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 = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);//搜索范围是包括子树
            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 {
                    //根据查询到的用户与输入的密码连接LDAP,用户密码正确才能连接
                    ctx = ldapContextFactory.getLdapContext(principal, credentials);
                    dealUser(userName, password);
                } catch (NamingException e) {
                    throw new IncorrectCredentialsException();
                }
                // 校验用户名密码
                if(StringUtils.isNotBlank(mail)){
                	User user = getSystemService().getUserByMail(mail);
                	if (user != null) {
            			if (Global.NO.equals(user.getLoginFlag())){
            				throw new AuthenticationException("msg:该已帐号禁止登录.");
            			}
            			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+"未找到邮箱对应的账号");
            		}
                }else{
                	throw new AuthenticationException("msg:"+"ldap未配置用户的邮箱");
                }
            }
        } finally {
            //关闭连接
            LdapUtils.closeContext(systemCtx);
            LdapUtils.closeContext(ctx);
        }
    }
    /**
	 * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		Principal principal = (Principal) getAvailablePrincipal(principals);
		// 获取当前已登录的用户
		if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){
			Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession());
			if (sessions.size() > 0){
				// 如果是登录进来的,则踢出已在线用户
				if (UserUtils.getSubject().isAuthenticated()){
					for (Session session : sessions){
						getSystemService().getSessionDao().delete(session);
					}
				}
				// 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。
				else{
					UserUtils.getSubject().logout();
					throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。");
				}
			}
		}
		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())){
					// 添加基于Permission的权限信息
					for (String permission : StringUtils.split(menu.getPermission(),",")){
						info.addStringPermission(permission);
					}
				}
			}
			// 添加用户权限
			info.addStringPermission("user");
			// 添加用户角色信息
			for (Role role : user.getRoleList()){
				info.addRole(role.getEnname());
			}
			// 更新登录IP和时间
			getSystemService().updateUserLoginInfo(user);
			// 记录登录日志
			LogUtils.saveLog(Servlets.getRequest(), "系统登录");
			return info;
		} else {
			return null;
		}
	}
    
    /**
     * 将LDAP查询到的用户保存到sys_user表
     *
     * @param userName
     */
    private void dealUser(String userName, String password) {
        if (StringUtils.isEmpty(userName)) {
            return;
        }
        //TO DO...
    }
    
    /**
     * 获取权限码
     *
     * @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();
        //查询数据库的用户权限
        //......

        authorizationMap.put("permissions", codeSet);
        session.setAttribute("permissions", codeSet);
        logger.debug("当前登录账户:{}的权限集合:{}", username, codeSet);
        return authorizationMap;
    }

    /**
	 * 获取系统业务对象
	 */
	public SystemService getSystemService() {
		if (systemService == null){
			systemService = SpringContextHolder.getBean(SystemService.class);
		}
		return systemService;
	}
    
    
}

  1. spring-context-shiro.xml配置

<!-- 定义Shiro安全管理配置 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<!-- <property name="realm" ref="systemAuthorizingRealm" /> -->
		<property name="sessionManager" ref="sessionManager" />
		<property name="cacheManager" ref="shiroCacheManager" />
		<property name="authenticator" ref="authenticator"/>
		<property name="realms">
            <list>
                <ref bean="systemAuthorizingRealm" />
                <ref bean="ldapAuthorizingRealm" />
            </list>
        </property>
	</bean>
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean>
        </property>
    </bean>
	
	<!-- 重写ldap认证 这里的rootDN是搜索公司的员工的根目录-->
    <bean id="ldapAuthorizingRealm" class="com.sijibao.bams.modules.sys.security.LdapAuthorizingRealm">
        <property name="rootDN" value="ou=Sijibao,dc=sijibao,dc=com"/>
        <property name="userDnTemplate" value="{0}"/>
        <property name="contextFactory" ref="contextFactory"/>
    </bean>

    <!-- 配置ldap路径及配置一个默认的用户和密码 -->
    <bean id="contextFactory" class="org.apache.shiro.realm.ldap.JndiLdapContextFactory">
        <property name="url" value="ldap://172.31.255.250:389/"/>
        <property name="systemUsername" value="CN=*****,OU=运营平台产品线,OU=产品研发中心,OU=Sijibao,DC=sijibao,DC=com"/>
        <property name="systemPassword" value="******"/>
    </bean>

Shiro 认证策略,如果有多个Realm,怎样才算是认证成功,这就需要认证策略。
AuthenticationStrategy接口的默认实现:

  • FirstSuccessfulStrategy:只要有一个Realm 验证成功即可,只返回第一个Realm 身份验证成功的认证信息,其他的忽略;
  • AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份验证成功的认证信息;
  • AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
  • ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略

参考链接

  1. Shiro-多Realm验证
  2. shiro使用LDAP认证
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
免费试听地址:B站搜索JeeGit观看《JeeSite4.x数据权限教程》、《JeeSite1.2.7系列基础教程》、《JeeSite4.x系列基础教程》等相关课程! 郑重声明:购课前,请认真听完第一章 课程简介 建议实战人群直接听:第九章、第十章 学生人群、刚入门:全听       数据权限主要讲解内容包含第一章 课程简介31.1 课程目标31.2 适用人群31.3 课程简介31.4 环境要求31.5 课程知识点大全31.6 课程售价31.7 购课声明31.8 资源清单31.9 售后方式41.10 讲师介绍4第二章 权限基础42.1 权限模型概述4第三章 JeeSite权限管理模型123.1 JeeSite1.2.7 权限管理模型123.2 JeeSite4.x 权限管理模型123.3 JeeSite4.x权限设计的扩展13第四章 用户管理144.1 JeeSite4.x内置用户类型144.1.1 用户管理思路144.1.2 网站会员、员工、单位、个人登录视图配置154.2 用户数据权限类型164.3实战训练、调试、日志查看16第五章 机构管理16第六章 角色管理186.1 JeeSite4.x角色管理概述186.2 JeeSite4.x越级授权与菜单权重186.3 JeeSite4.x 越级授权可能存在的隐患极其解决方案196.4用户表如何区分非管理员、系统管理员、二级管理员206.5 角色权限注意事项206.6 角色授权数据范围使用注意事项216.7 为何用户不设置员工权限无效?236.8 岗位管理与角色分类的岗位分类与角色分类有何区别?23第七章 二级管理员23第八章 系统管理员238.1 系统管理员238.2 总结:何时使用超级管理员、系统管理员、二级管理员?23第九章 Jeesite数据权限调用239.1 JeeSite4.x数据调用基础239.2 JeeSite4.x 实现数据列权限推荐解决方案249.3多数源模式下数据权限bug简易解决方案249.4 JeeSite4.x 自定义扩展数据权限249.5支持全球地区、全球企业、全球机构、全球部门授权24第十章 JeeSite数据权限实战2410.1 案例一2410.2 案例二2410.3 案例三2510.4 案例四2510.5 案例五25第十一章 JeeSite4.x常见问题解答251.1数据权限管理的代码会公开吗,购买了能看吗?251.2 JeeSite数据权限教程是Thinkgem录制的吗?25第十二章 参考阅读2612.1、JeeSite官方文档2712.2、美国国家标准与技术研究院2712.3、中国国家标准化管理委员会2712.4、ITSEC欧洲安全评价标准2812.5、百度学术2812.6、开源框架2912.6.1 JeeSite2912.6.2 Casbin2912.6.3 Eladmin2912.6.4 Spring-boot-demo2912.6.5 Jeeplatform3012.6.6 Pig3012.6.7 Jeecg-boot3012.6.8 Jfinal3012.6.9 Guns3112.6.10 Zheng3112.6.11 Cloud-Platform3112.7 博文资源31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值