有这样一个需求,两地同时使用一个账号登录,需要将先登录的用户的session删除
流程分析
- 用户登录时判断是否之前改账号在别的地方登录
- 若没有登录,直接进行登录
- 若有登录,则找到登录的session,给该session做个标记
- 当之前登录的用户再次进行操作时,判断其是否有标记,有则删除其session,并返回友好提示
自定义登录验证
/**
* 自定义realm,用于用户登录的验证和授权
*/
public class MyRealm extends AuthorizingRealm {
private static final Logger LOG = LoggerFactory.getLogger(MyRealm.class);
@Autowired
private UserService userService;
/**
* 用户授权方法,在调用 hasRole hasRoles hasPermission hasPermissions checkRole
* isRole等方法时被调用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 暂时不加入权限到登录用户,防止验证不过,返回一个空的 AuthorizationInfo 对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
try {
info.setStringPermissions(userService.queryPermissionsByUserId(ShiroUtils.getLoginUser().getId()));
} catch (Exception e) {
throw new PlatformException("授权失败");
}
return info;
}
/**
* 用户认证方法,在调用login方法后,调用该方法
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UserEntity user = null;
String formUsername = (String) token.getPrincipal();
try {
user = userService.queryUserByUserName(formUsername);
} catch (Exception e) {
LOG.error("用户认证失败:",e);
throw new PlatformException("认证失败");
}
if (user == null) {
throw new UnknownAccountException("用户不存在");// 用户不存在
}
if (user.getIsDisable() == 2) {
throw new PlatformException("用户被禁用");
}
AuthenticationInfo info = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), getName());
// 放到shiro的session中
setSession(LoginString.LOGIN_KEY, user,user.getUserName());//LoginString.LOGIN_KEY是一个常量值,随意定义即可
return info;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
/**
* 初始化shiro的加密方式
*/
public void initCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(2);
setCredentialsMatcher(credentialsMatcher);
}
/**
* 将特殊数据放入到shiro的session中,让shiro来进行管理
*
* @param key
* @param value
*/
private void setSession(Object key, Object value,String userName) {
Subject subject = SecurityUtils.getSubject();
DefaultWebSecurityManager defaultWebSecurityManager = (DefaultWebSecurityManager)SecurityUtils.getSecurityManager();
DefaultWebSessionManager defaultWebSessionManager = (DefaultWebSessionManager)defaultWebSecurityManager.getSessionManager();
Collection<Session> sessions = defaultWebSessionManager.getSessionDAO().getActiveSessions();
if (null != subject) {
Session session = subject.getSession();
if (null != session) {
session.setAttribute(key, value);
}
/**
* 控制同一用户只能在一个地方登录,后登录的用户会顶掉开始登录的用户
* 注意:如果用户量过大时此方法会有效率问题,需要改进
*/
for(Session oldSession:sessions){
UserEntity entity = (UserEntity)oldSession.getAttribute(LoginString.LOGIN_KEY);
if(entity!=null){
String oldName = entity.getUserName();
String oldSessionId = oldSession.getId().toString();
if(oldName.equals(userName) && !oldSessionId.equals(session.getId().toString())){
oldSession.setAttribute("repeatLogin",true);
// oldSession.stop();若简单来处理直接在此处将session销毁即可
}
}
}
}
}
}
统一返回数据类型
系统的所有返回均为同一实体,将接口的数据放入实体的属性中即可,这样方便与前台的对接,参考代码如下,这样返回时可以根据当前的session判断是否已经被人挤掉了。
package com.neusoft.sensteer.fleet.common.consts;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
/**
* 接口返回的封装对象
*/
public class ReturnBean<T> {
/**
* 执行结果:0成功1失败
*/
private String code;
/**
* 描述(错误原因)
*/
private String message;
/**
* 返回对象
*/
private T userData;
public ReturnBean(){
}
public ReturnBean(T userData){
this.code = "0";
this.message = "成功";
this.userData = userData;
handleSession();
}
public ReturnBean(String code, String remark){
this.code = code;
this.message = remark;
handleSession();
}
public ReturnBean(T exception,String remark){
this.code = "1";
this.message = remark;
this.userData = null;
handleSession();
}
private void handleSession(){
Session session = SecurityUtils.getSubject().getSession();
if(session!=null){
Object o = session.getAttribute("repeatLogin");
if(o!=null){
session.stop();
this.code="3";
this.message="您的账号在其他地方登录,如非本人操作,建议修改用户密码!";
this.userData=null;
}
}
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getUserData() {
return userData;
}
public void setUserData(T userData) {
this.userData = userData;
}
}
核心代码:shiro获取当前用户所有session
这是一种暴力的获取方式,若系统用户量小可以考虑,若百万千万级别,还是建议选择其他方式
/**
* 将特殊数据放入到shiro的session中,让shiro来进行管理
*
* @param key
* @param value
*/
private void setSession(Object key, Object value,String userName) {
Subject subject = SecurityUtils.getSubject();
DefaultWebSecurityManager defaultWebSecurityManager = (DefaultWebSecurityManager)SecurityUtils.getSecurityManager();
DefaultWebSessionManager defaultWebSessionManager = (DefaultWebSessionManager)defaultWebSecurityManager.getSessionManager();
Collection<Session> sessions = defaultWebSessionManager.getSessionDAO().getActiveSessions();
if (null != subject) {
Session session = subject.getSession();
if (null != session) {
session.setAttribute(key, value);
}
/**
* 控制同一用户只能在一个地方登录,后登录的用户会顶掉开始登录的用户
* 注意:如果用户量过大时此方法会有效率问题,需要改进
*/
for(Session oldSession:sessions){
UserEntity entity = (UserEntity)oldSession.getAttribute(LoginString.LOGIN_KEY);
if(entity!=null){
String oldName = entity.getUserName();
String oldSessionId = oldSession.getId().toString();
if(oldName.equals(userName) && !oldSessionId.equals(session.getId().toString())){
oldSession.setAttribute("repeatLogin",true);
// oldSession.stop();若简单来处理直接在此处将session销毁即可
}
}
}
}
}