springboot+shiro实现登录人数控制

今天又是美好的一天,好了,不多逼逼。今天内容有点多,咱们抓紧时间。
好,咱们来讲讲如何写springboot+shiro 实现登陆人数控制
来,咱们直接看代码
在这里插入图片描述

conrtoller:咱们就是一个正常登陆,没什么区别

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password);
System.out.println("登录成功");
subject.login(usernamePasswordToken);

MyRealm看一看就可以了,没什么改动
在这里插入图片描述
KickoutSessionControlFilter 它是核心,我copy,不要喷我

  public class KickoutSessionControlFilter extends AccessControlFilter {

    /** 踢出后到的地址 */
    private String kickoutUrl;

    /**  踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 */
    private boolean kickoutAfter = false;

    /**  同一个帐号最大会话数 默认1 */
    private int maxSession = 1;
    private SessionManager sessionManager;

    private Cache<String, Deque<Serializable>> cache;

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setCacheManager(CacheManager cacheManager) {
        this.cache = cacheManager.getCache("shiro-activeSessionCache");
    }

    /**
     * 是否允许访问,返回true表示允许
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)
            throws Exception {
        return false;
    }

    /**
     * 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
            throws Exception {
        System.out.println("=======================我进来了================================");
        Subject subject = getSubject(request, response);
        System.out.println(subject);
        System.out.println(subject.isAuthenticated() && !subject.isRemembered());
        if(!subject.isAuthenticated() && !subject.isRemembered()) {
            //如果没有登录,直接进行之后的流程
            return true;
        }

        Session session = subject.getSession();

        //这里获取的User是实体 因为我在 自定义ShiroRealm中的doGetAuthenticationInfo方法中
        //new SimpleAuthenticationInfo(user, password, getName()); 传的是 User实体 所以这里拿到的也是实体,如果传的是userName 这里拿到的就是userName
        String username = ((String) subject.getPrincipal());
        Serializable sessionId = session.getId();
        System.out.println(sessionId);

        // 初始化用户的队列放到缓存里
        Deque<Serializable> deque = cache.get(username);
        if(deque == null) {
            deque = new LinkedList<Serializable>();
            cache.put(username, deque);
        }

        //如果队列里没有此sessionId,且用户没有被踢出;放入队列
        if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
            deque.push(sessionId);
        }

        //如果队列里的sessionId数超出最大会话数,开始踢人
        System.out.println(deque.size());
        while(deque.size() > maxSession) {
            Serializable kickoutSessionId = null;
            if(kickoutAfter) { //如果踢出后者
                kickoutSessionId=deque.getFirst();
                kickoutSessionId = deque.removeFirst();
                System.out.println("===============================踢出去了=============================");
            } else { //否则踢出前者
                kickoutSessionId = deque.removeLast();
                System.out.println("===============================踢出去了=============================");
            }
            try {
                Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                if(kickoutSession != null) {
                    //设置会话的kickout属性表示踢出了
                    kickoutSession.setAttribute("kickout", true);
                }
            } catch (Exception e) {//ignore exception
                e.printStackTrace();
            }
        }

        //如果被踢出了,直接退出,重定向到踢出后的地址
        if (session.getAttribute("kickout") != null) {
            //会话被踢出了
            try {
                subject.logout();
            } catch (Exception e) {
            }
            WebUtils.issueRedirect(request, response, kickoutUrl);
            return false;
        }
        return true;
    }
}

简单说一下继承AccessControlFilter接口,重写里面的两个方法,isAccessAllowed return 返回true 的话就直接跑了,不会拦截,所以咱们都会设置成false,这样子,他就会跑到onAccessDenied 里面,就会执行咱们的代码了。
**

注意:

**
this.cache = cacheManager.getCache(“shiro-activeSessionCache”); 这个地方注意一下,如果你这个cacheManager.getCache(“shiro-activeSessionCache”);地方话有一个地方需要注意,private Cache<String, Deque> cache; 导包要shiro的那个,别导错了,否则会飘红…

ShiroConfig

 //自定义MyRealm
    @Bean
    public MyRealm myAuthRealm(){
        try {
            MyRealm myRealm = new MyRealm();
            System.out.println("======================请稍后====================================================");
            Thread.sleep(2000);
            System.out.println("=================================myRealm注册完成================================");
            Thread.sleep(1000);
            return myRealm;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
 /**
     * 控制并发人数
     * @return
     */
    @Bean
    public KickoutSessionControlFilter kickoutSessionControlFilter(){
        System.out.println("============控制并发人数");
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        //用于根据会话ID,获取会话进行踢出操作的;
        kickoutSessionControlFilter.setSessionManager(securityManager());
        //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;
        kickoutSessionControlFilter.setCacheManager(ehCacheManager());
        //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;
        kickoutSessionControlFilter.setKickoutAfter(false);
        //同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
        kickoutSessionControlFilter.setMaxSession(1);
        //被踢出后重定向到的地址;
        kickoutSessionControlFilter.setKickoutUrl("/user/pcLogin?kickout=1");
        return kickoutSessionControlFilter;
    }
	 /**
     * 缓存
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return cacheManager;
    }

额。。。 这个xml文件我怎么给你??(网上找找,找不到去我网盘里面拿去吧!)
需要放在resources下面
点击这里宝贝
提取码:nscb

@Bean
    public EnterpriseCacheSessionDAO enterCacheSessionDAO() {
        EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();
        //添加缓存管理器
        //enterCacheSessionDAO.setCacheManager(ehCacheManager());
        //添加ehcache活跃缓存名称(必须和ehcache缓存名称一致)
        enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        return enterCacheSessionDAO;
    }
 //配置安全管理器 SecurityManager:
    @Bean
    public DefaultWebSecurityManager securityManager() {

        try {
            // 将自定义 Realm 加进来
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
            System.out.println("======================请稍后====================================================");
            Thread.sleep(2000);
            System.out.println("==============================securityManager注册完成===========================================");
            Thread.sleep(1000);
            return securityManager;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

如果你产生了这个错误:org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.,请添加下面这段代码。
具体原因,不详。如有大佬知道为啥,请在底下告诉我。 感激不尽!!!

    /**
     * 添加万这个就会让控制登录接口不回报错,原因不详
     * @return
     */
    @Bean
    public FilterRegistrationBean delegatingFilterProxy(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }

filterChainMap.put("/**", “kickout,authc”); 这个全部拦截啥的 前面需要加上kickout。

 @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
        // 定义 shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();


        // 设置自定义的 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置默认登录的 URL,身份认证失败会访问该 URL
        shiroFilterFactoryBean.setLoginUrl("/user/pcLogin");

        //自定义拦截器限制并发人数
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        //限制同一帐号同时在线的个数
        filtersMap.put("kickout", kickoutSessionControlFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);


        // LinkedHashMap 是有序的,进行顺序拦截器配置
        Map<String,String> filterChainMap = new LinkedHashMap<>();

        // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
        filterChainMap.put("/swagger-*/**", "anon");
        filterChainMap.put("/v2/**", "anon");
        filterChainMap.put("/swagger-resources/**", "anon");
        filterChainMap.put("/swagger-ui.html/**", "anon");
        // 登录 URL 放行
        filterChainMap.put("/user/pclogin", "anon");

        //全部拦截(认证)
        filterChainMap.put("/**", "kickout,authc");
        // 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        System.out.println("=======================================shiroFilterFactoryBean注册完成===================================");
        return shiroFilterFactoryBean;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值