There is no session with id session多人使用一个账号

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();
    }
原文地址:https://www.cnblogs.com/thinkingandworkinghard/p/12530007.html