天天看点

Shiro单用户登录

有这样一个需求,两地同时使用一个账号登录,需要将先登录的用户的session删除

 流程分析

  1. 用户登录时判断是否之前改账号在别的地方登录
  2. 若没有登录,直接进行登录
  3. 若有登录,则找到登录的session,给该session做个标记
  4. 当之前登录的用户再次进行操作时,判断其是否有标记,有则删除其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销毁即可
					}
				}
			}


		}


	}
           

继续阅读