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(); }