说明:一个相同的账号在第一个浏览器登陆成功后,当在第二浏览器登陆成功后,第一个浏览器登陆的用户就会被踢出,无法进行操作。
主要是配置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包,请留言!谢谢!