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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM