有這樣一個需求,兩地同時使用一個賬号登入,需要将先登入的使用者的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銷毀即可
}
}
}
}
}