springboot shiro session互踢功能,一个用户只能一处浏览器登录

说明:一个相同的账号在第一个浏览器登陆成功后,当在第二浏览器登陆成功后,第一个浏览器登陆的用户就会被踢出,无法进行操作。

主要是配置EhCache缓存器来缓存sission

1、shiro 配置Javabean,ShiroConfiguration.java



import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.grg.inv.usr.service.shiro.ShiroAuthorizingRealm;
import com.grg.inv.usr.util.param.ParamKey;

import net.sf.ehcache.CacheManager;

@Configuration
public class ShiroConfiguration {

	@Bean(name = "securityManager")
	public SecurityManager securityManager(@Qualifier("authRealm") ShiroAuthorizingRealm authRealm,
			@Qualifier("cookieRememberMeManager") CookieRememberMeManager cookieRememberMeManager,
			EhCacheManager ehCacheManager,DefaultWebSessionManager defaultWebSessionManager) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(authRealm);
		securityManager.setRememberMeManager(cookieRememberMeManager);
	    securityManager.setCacheManager(ehCacheManager);
	    securityManager.setSessionManager(defaultWebSessionManager);
		return securityManager;
	}
    @Bean
    public EhCacheManager ehCacheManager(CacheManager cacheManager) {
        EhCacheManager em = new EhCacheManager();
        //将ehcacheManager转换成shiro包装后的ehcacheManager对象
        em.setCacheManager(cacheManager);
        //em.setCacheManagerConfigFile("classpath:ehcache.xml");
        return em;
    }
	@Bean(name = "authRealm")
	public ShiroAuthorizingRealm myAuthRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
		ShiroAuthorizingRealm myAuthorizingRealm = new ShiroAuthorizingRealm();
		myAuthorizingRealm.setCredentialsMatcher(matcher);
		return myAuthorizingRealm;
	}
	   //添加的
    @Bean(name = "memorySessionDAO")
    public MemorySessionDAO memorySessionDAO() {
        MemorySessionDAO memorySessionDAO = new MemorySessionDAO();
        return memorySessionDAO;
    }
    
    @Bean(name = "defaultWebSessionManager")
    public DefaultWebSessionManager defaultWebSessionManager(@Qualifier("memorySessionDAO") MemorySessionDAO memorySessionDAO) {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionDAO(memorySessionDAO);
        defaultWebSessionManager.setGlobalSessionTimeout(Integer.valueOf(ParamKey.getUserOverTime())*1000);
        return defaultWebSessionManager;
    }

	/**
	 * cookie对象;
	 * 
	 * @return
	 */
	@Bean
	public SimpleCookie rememberMeCookie() {
		SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
		simpleCookie.setMaxAge(259200);
		return simpleCookie;
	}

	/**
	 * 记住我管理器 cookie管理对象;
	 * 
	 * @return
	 */
	@Bean(name = "cookieRememberMeManager")
	public CookieRememberMeManager rememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(rememberMeCookie());
		return cookieRememberMeManager;
	}

	/**
	 * 密码匹配凭证管理器
	 * 
	 * @return
	 */
	@Bean(name = "hashedCredentialsMatcher")
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");
		return hashedCredentialsMatcher;
	}

	/**
	 * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; Controller才能使用@RequiresPermissions
	 * 
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
			@Qualifier("securityManager") SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	@Bean
	public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);

		// 拦截器.
		Map<String, String> map = new LinkedHashMap<String, String>();
		map.put("/**.op", "anon");
		map.put("/logout", "logout");
		map.put("/**", "authc");
		shiroFilterFactoryBean.setLoginUrl("/unLogin.op");

		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		// shiroFilterFactoryBean.setLoginUrl("/login");
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
		return shiroFilterFactoryBean;
	}
}

2、继承AuthorizingRealm,重新AuthorizingRealm



import java.util.HashMap;
import java.util.Map;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.grg.inv.usr.db.model.autogen.User;
import com.grg.inv.usr.service.user.UserService;
import com.grg.inv.usr.util.RedisUtils;

/**
 * 
 *
 * @author 
 * @version 创建时间:2017年5月8日 上午10:50:50 类说明: --
 */
public class ShiroAuthorizingRealm extends AuthorizingRealm {
    
    private static Logger logger  =LoggerFactory.getLogger(ShiroAuthorizingRealm.class);
    
	@Autowired
	private UserService userService; // 从数据库获取登陆账号密码
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		return authorizationInfo;
	}
	protected final Map<Object, Object> sessionMap = new HashMap<Object, Object>();

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// TODO Auto-generated method stub
	    if (token instanceof UsernamePasswordToken) {
			UsernamePasswordToken userpwdToken = (UsernamePasswordToken) token;
			String account = (String) userpwdToken.getPrincipal();
			final User user = userService.authenticate(account);//获取数据库的账号密码,与页面登陆账号密码进行验证
			if (user == null) {
				throw new UnknownAccountException("用户不存在!");
			}
			if (null != user && (null == user.getStatus() || user.getStatus() != 1)) {
			    throw new ExcessiveAttemptsException("用户已锁定!");
            }
			return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
		} else {
			throw new UnknownAccountException("用户不存在!");
		}
	}

}

3、创建ehcache.xml,放在resources下

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
  updateCheck="false">
          <diskStore path="java.io.tmpdir/Tmp_EhCache" />
           <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
    timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />

            <cache name="demo" eternal="false" maxElementsInMemory="100" overflowToDisk="false" diskPersistent="false"
    timeToIdleSeconds="0" timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU" />

</ehcache>

4、创建一个登陆controller 接收账号密码,LoginController.java

public class LoginController {


    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private HttpSession session;
    @Autowired
    @Qualifier("memorySessionDAO")
    private SessionDAO sessionDAO;
    
    /**
     * 用户登录
     * 
     * @param account
     * @param password
     * @param randomCode
     */
    @ResponseBody
    @RequestMapping(value = "/login.op",method=RequestMethod.POST)
    public ResponseDes login(String account, String password, String authCode) {
        ResponseDes result = new ResponseDes();
        String randCheckCode = (String) session.getAttribute("verCode");
        // 验证码判断
        logger.info("randCheckCode:"+randCheckCode+"############autCode:"+authCode+"#########account:"+account+"#######password:"+password);
        // 进行单账号互踢功能
        Collection<Session> sessions = sessionDAO.getActiveSessions(); // 获取存在的所有SESSION账号
        logger.info("There are {} several caches ", sessions.size());
        Iterator<Session> it = sessions.iterator();            
        while (it.hasNext()) {                                       // session 进行遍历
            Session session = it.next();
            SimplePrincipalCollection simplePrincipalCollection = (SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            logger.info("There are ={}= simplePrincipalCollection ", simplePrincipalCollection);
            if (Objects.nonNull(simplePrincipalCollection)) {        // 如果不为null 则判断账号密码是否一样,一样的剔除前个登陆账号
                User user = (User) simplePrincipalCollection.getPrimaryPrincipal();
                if (user.getAccount().equals(account) && user.getPassword().equals(MD5Util.MD5(password).toLowerCase())) {
                    session.setTimeout(100);// 注意,这里不能设置为0,如果设置为0,会下面shiro授权报错的
                }
            }
        }
        // 进行登录验证
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(account.trim(), password.trim().toCharArray()));
        } catch (UnknownAccountException e) {
            result.setCode(ResponseCode.NOTEXISTUSER.getCode());
            result.setDescribe(ResponseCode.NOTEXISTUSER.getValue());
            return result;
        } catch (ExcessiveAttemptsException e) {
            result.setCode(ResponseCode.LOCKUSER.getCode());
            result.setDescribe(ResponseCode.LOCKUSER.getValue());
            return result;
        } catch (IncorrectCredentialsException e) {
            result.setCode(ResponseCode.ILLEGALPASS.getCode());
            result.setDescribe(ResponseCode.ILLEGALPASS.getValue()+",您24小时内还有"+"次机会!");
        return result;
    }
}

6、在启动类注入@EnableCaching


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@MapperScan("com.ccc.ccc")
@ComponentScan({ "com" })
@EnableCaching
public class FaceApplication {

    public static void main(String[] args) {
        SpringApplication.run(FaceApplication.class, args);
    }

}

7、提供MD5Util.java



import java.security.MessageDigest;

/**
 * @author zhongyi
 * 2017年11月15日
 * MD5Util
 */
public class MD5Util {

	public static String MD5(String key) {
        char hexDigits[] = {
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
        };
        try {
            byte[] btInput = key.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            return null;
        }
    }
}

 

6、异常说明

There is no session with id [f8e19ddd-aaf8-4784-826b-09d14504912b]

对于该功能来说,你把这个会话已经是空值了,那么你就无法进行下步shiro的验证,你需要把 session.setTimeout(0);  设置为 session.setTimeout(100);   有部分时间,让shiro的账号密码验证完,再杀死这个session。

 

 

备注:因为不是每个人的环境是一模一样的,有些人可能不能正常运行,但是我保证这个代码在我程序是正常运行的。如果缺少java或者.jar包,请留言!谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值