zoukankan      html  css  js  c++  java
  • 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();
        }
  • 相关阅读:
    Android 系统开发做什么?
    MySQL索引-B+树
    转:redis雪崩、穿透、击穿
    转:django3上线部署踩得坑
    nginx、uwsgi部署django中session丢失得问题
    类型转换(数字转字符串等)
    JS基础篇1:数据类型(8种)
    css3动画与js动画的区别
    drag拖拽事件
    三栏布局,中间自适应
  • 原文地址:https://www.cnblogs.com/thinkingandworkinghard/p/12530007.html
Copyright © 2011-2022 走看看