天天看點

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銷毀即可
					}
				}
			}


		}


	}
           

繼續閱讀