1.問題場景:在dev和test環境開發時候,分配的賬號是多人共用的,當一個人修改權限后,調用shiro的清楚服務器sesionId后,當其他人再次修改權限信息時候,由於服務器的sessionId已經被全部清空,就會報 There is no session with id "XXX"的問題
2.解決方式:網上說的一般是由於SESSIONID和比如tomcat/jetty等使用的sessionId同名導致的,這個是一個原因。不過我的原因是由於服務器所有的sessionId被清空了導致的,所以做了限制:當一個用戶登錄時候,我會先清空這個賬號的所有緩存信息,這樣不會導致一個賬號在多個地方登錄。不過有些簡單
package com.sq.transportmanage.gateway.service.common.shiro.realm; import com.google.common.collect.Maps; import com.sq.transportmanage.gateway.dao.entity.driverspark.CarAdmUser; import com.sq.transportmanage.gateway.dao.entity.driverspark.SaasPermission; import com.sq.transportmanage.gateway.dao.mapper.driverspark.ex.SaasPermissionExMapper; import com.sq.transportmanage.gateway.dao.mapper.driverspark.ex.SaasRoleExMapper; import com.sq.transportmanage.gateway.service.auth.MyDataSourceService; import com.sq.transportmanage.gateway.service.common.constants.Constants; import com.sq.transportmanage.gateway.service.common.constants.SaasConst; import com.sq.transportmanage.gateway.service.common.dto.SaasPermissionDTO; import com.sq.transportmanage.gateway.service.common.shiro.session.RedisSessionDAO; import com.sq.transportmanage.gateway.service.util.BeanUtil; import com.sq.transportmanage.gateway.service.util.MD5Utils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; 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.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.support.DefaultSubjectContext; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.security.NoSuchAlgorithmException; import java.util.*; /**認證 與 權限 **/ /** * 這個就是shiro SSOLogin 的用戶獲取的屬性配置 */ @Component public class UsernamePasswordRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(UsernamePasswordRealm.class); @Autowired private MyDataSourceService myDataSourceService; @Autowired private SaasPermissionExMapper saasPermissionExMapper; @Autowired private SaasRoleExMapper saasRoleExMapper; @Autowired @Qualifier("sessionDAO") private RedisSessionDAO redisSessionDAO; /**重寫:獲取用戶的身份認證信息**/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{ logger.info( "[獲取用戶的身份認證信息開始]authenticationToken="+authenticationToken); try { UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; CarAdmUser adMUser = myDataSourceService.queryByAccount(token.getUsername()); //處理session 防止一個賬號多處登錄 try { redisSessionDAO.clearRelativeSession(null,null,adMUser.getUserId()); } catch (Exception e) { logger.info("=========清除session異常============"); } SSOLoginUser loginUser = new SSOLoginUser(); loginUser.setId( adMUser.getUserId() ); loginUser.setLoginName( adMUser.getAccount() ); loginUser.setMobile( adMUser.getPhone() ); loginUser.setName( adMUser.getUserName() ); loginUser.setEmail(adMUser.getEmail()); loginUser.setType(null); // loginUser.setStatus( adMUser.getStatus() ); loginUser.setAccountType( adMUser.getAccountType() ); loginUser.setLevel(adMUser.getLevel()); loginUser.setMerchantId(adMUser.getMerchantId()); loginUser.setSupplierIds(adMUser.getSuppliers()); String md5= null; try { md5 = MD5Utils.getMD5DigestBase64(loginUser.getMerchantId().toString()); } catch (NoSuchAlgorithmException e) { logger.info("sign error" + e); } if(Constants.MANAGE_MD5.equals(md5)){ loginUser.setSuper(true); }else { loginUser.setSuper(false); } List<String> menuUrlList = saasPermissionExMapper.queryPermissionMenussOfUser(adMUser.getUserId()); loginUser.setMenuUrlList(menuUrlList); /**當前用戶所擁有的菜單權限**/ List<Integer> permissionIds = saasPermissionExMapper.queryPermissionIdsOfUser(adMUser.getUserId()); List<Byte> permissionTypes = Arrays.asList( new Byte[] { SaasConst.PermissionType.MENU }); Map<Integer,List<SaasPermissionDTO>> mapPermission = Maps.newHashMap(); /**查詢所有的一級菜單**/ if(!CollectionUtils.isEmpty(permissionIds)){ List<SaasPermission> permissionList = saasPermissionExMapper.queryModularPermissions(permissionIds); Map<Integer,String> map = Maps.newHashMap(); permissionList.forEach(list ->{ map.put(list.getPermissionId(),list.getPermissionName()); //查詢所有一級菜單下的子菜單 以樹形結果返回 List<SaasPermissionDTO> menuPerms = this.getChildren( permissionIds , list.getPermissionId(), permissionTypes); mapPermission.put(list.getPermissionId(),menuPerms); }); loginUser.setMenuPermissionMap(map); loginUser.setMapPermission(mapPermission); } // //---------------------------------------------------------------------------------------------------------數據權限BEGIN logger.info( "[獲取用戶的身份認證信息]="+loginUser); return new SimpleAuthenticationInfo(loginUser, authenticationToken.getCredentials() , this.getName() ); } catch (Exception e) { logger.error("獲取用戶的身份認證信息異常",e); return null; } } /** * 查詢每個一級菜單下的子菜單 * @param permissionIds * @param parentPermissionId * @param permissionTypes tree 樹形,list 列表 * @return */ private List<SaasPermissionDTO> getChildren( List<Integer> permissionIds, Integer parentPermissionId, List<Byte> permissionTypes ){ List<SaasPermission> childrenPos = saasPermissionExMapper.queryPermissions(permissionIds, parentPermissionId, null, permissionTypes, null, null); if(childrenPos==null || childrenPos.size()==0) { return null; } //遞歸 List<SaasPermissionDTO> childrenDtos = BeanUtil.copyList(childrenPos, SaasPermissionDTO.class); Iterator<SaasPermissionDTO> iterator = childrenDtos.iterator(); while (iterator.hasNext()) { SaasPermissionDTO childrenDto = iterator.next(); List<SaasPermissionDTO> childs = this.getChildren( permissionIds, childrenDto.getPermissionId() , permissionTypes ); childrenDto.setChildPermissions(childs); } return childrenDtos; } /** * 查詢角色登錄進來所擁有的菜單時候shiro實現 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SSOLoginUser loginUser = (SSOLoginUser) principalCollection.getPrimaryPrincipal(); String account = loginUser.getLoginName(); //登錄名 List<String> perms_string = saasPermissionExMapper.queryPermissionCodesOfUser( loginUser.getId() ); List<String> roles_string = saasRoleExMapper.queryRoleCodesOfUser( loginUser.getId() ); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<String>( roles_string ); authorizationInfo.setRoles( roles ); logger.info( "[獲取用戶授權信息(角色)] "+account+"="+roles); Set<String> perms = new HashSet<String>( perms_string ); authorizationInfo.setStringPermissions(perms); logger.info( "[獲取用戶授權信息(權限)] "+account+"="+perms); return authorizationInfo; } @Override public Object getAuthorizationCacheKey(PrincipalCollection principals) { SSOLoginUser loginUser = (SSOLoginUser) principals.getPrimaryPrincipal(); String account = loginUser.getLoginName(); //登錄名 return "-AuthInfo-"+account; } @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); } }
/**二、當權限信息、角色信息、用戶信息發生變化時,同時清理與之相關聯的會話**/ @MyDataSource(value = DataSourceType.DRIVERSPARK_MASTER) public void clearRelativeSession( final Integer permissionId, final Integer roleId, final Integer userId ) { final Cache<Serializable, Session> cache = super.getActiveSessionsCache(); //final Cache<Serializable, Session> cache = activeSessions; new Thread(new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { try{ //A:如果當權限發生變化時,查詢所關聯的全部角色ID List<Integer> roleIds = new ArrayList<Integer>(); if( permissionId!=null ) { roleIds = myDataSourceService.queryRoleIdsOfPermission( permissionId ); } //B:如果當角色發生變化時,查詢所關聯的用戶ID if( roleId !=null ) { roleIds.add(roleId); } List<Integer> userIds = new ArrayList<Integer>(); if( roleIds.size()>0 ) { userIds = myDataSourceService.queryUserIdsOfRole( roleIds ); } //C:如果當用戶發生變化時,查詢出這些用戶的登錄賬戶名稱 if( userId != null ) { logger.info("當用戶發生變化時清除緩存userId={}",userId); userIds.add(userId); } List<String> accounts = new ArrayList<String>(); if(userIds.size()>0) { accounts = myDataSourceService.queryAccountsOfUsers(userIds); } //D:匯總需要清理的REDIS KEY 和 sessionId if(accounts.size() ==0) { return; } Set<String> redisKeysNeedDelete = new HashSet<String>();//這是需要清除的所有REDIS KEY Set<String> allSessionIds = new HashSet<String>();//這是需要清除的所有的sessionId for( String account : accounts) { redisKeysNeedDelete.add( KEY_PREFIX_OF_SESSIONID + account ); Set<String> sessionIds = (Set<String>) redisTemplate.opsForValue().get(KEY_PREFIX_OF_SESSIONID+account); if(sessionIds!=null && sessionIds.size()>0) { allSessionIds.addAll(sessionIds); } } //E1:執行清除執久化的會話(這里是保存在REDIS中的) for( String sessionId : allSessionIds) { logger.info("執行清除REDIS的會話緩存sessionId={}",sessionId); redisKeysNeedDelete.add( KEY_PREFIX_OF_SESSION + sessionId ); } redisTemplate.delete(redisKeysNeedDelete); //E2:執行清理shiro會話緩存 if(cache!=null) { for(String sessionId : allSessionIds ){ SimpleSession session = (SimpleSession)cache.get(sessionId); if(session!=null) { session.setExpired(true); } logger.info("執行清理shiro會話緩存sessionId={}",sessionId); cache.remove(sessionId); } } //E3:執行清理shiro 認證與授權緩存 for( String account : accounts) { logger.info("執行清理shiro 認證與授權緩存account={}",account); //todo 此處不合理,應該用下面的代碼 這個是臨時方案:執行退出的操作 相當於手動點擊退出 這個觸發條件太廣泛了 要加很多邏輯判斷那些人需要退出 /*Subject subject = SecurityUtils.getSubject(); if(subject.isAuthenticated()) { subject.logout(); }*/ SSOLoginUser principal = new SSOLoginUser(); principal.setLoginName( account ); SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection( ); simplePrincipalCollection.add(principal, authorizingRealm.getName() ); ((UsernamePasswordRealm)authorizingRealm).clearCache( simplePrincipalCollection ); } }catch(Exception ex) { logger.error("清除緩存異常",ex); }finally { //DynamicRoutingDataSource.setDefault("mdbcarmanage-DataSource"); } } }).start(); }