springboot + shiro实现显示用户在线状态和限制账号只能在一个地方登录(用户踢出)同一个浏览器,用两个标签页分别登录,是同一个session。

1.shiro配置类

@Configuration
@Lazy
public class ShiroConfiguration {

    /**
     *shiro的过滤器配置
    */

    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new LoginAuthenticationFilter());

        //要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/");
        //登录成功后要跳转的连接,逻辑也可以自定义,例如返回上次请求的页面
        shiroFilterFactoryBean.setSuccessUrl("/home");
        //用户访问未对其授权的资源时,所显示的连接
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置退出过滤器,过滤按顺序执行,全局anon需要放到最后面

        /**
         * 1、建议配置URL地址过滤,可以快速过滤没有,不要等待注解层抛出异常
          *以下示例,实际根据需求来
         */
         filterChainDefinitionMap.put("/api/**", "anon");
        filterChainDefinitionMap.put("/client/**", "anon");
        filterChainDefinitionMap.put("/**.js", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**

    * 配置自己写的认证代码类

    */

    @Bean
    public ShiroDBAuthorizingRealm shiroRealm() {

        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setAuthenticationCachingEnabled(true);
        return shiroDBAuthorizingRealm;
    }

    /**
     * shiro的安全管理器
     *
     * @return
     * @Bean(name = "securityManager")
     */
    @Bean
    public SecurityManager securityManager(ShiroDBAuthorizingRealm shiroDBAuthorizingRealm, CacheManager cacheManager, Environment environment) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroDBAuthorizingRealm);
        boolean online = Boolean.valueOf(environment.getProperty("user.online","true"));
        if (online) {
            DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
            defaultWebSessionManager.setGlobalSessionTimeout(30 * 60 * 1000);
            securityManager.setSessionManager(defaultWebSessionManager);
        } else {
            securityManager.setSessionManager(new ServletContainerSessionManager());
        }
        return securityManager;
    
    }
    /**
     * Shiro生命周期处理器
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),
     * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     *
     * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

       online表示是否需要显示用户状态。

      这我使用online参数来决定使用哪一个session管理器,默认为ServletContainerSessionManager,session缓存到 WEB容器中,热加载不需要重新登录。(即我们重启项目时,不需要每次都重新登录,大大提高开发效率),而这个管理器中没有获取sessionDAO的方法,无法获取登录用户。所用使用了DefaultWebSessionManager,他继承了DefaultSessionManager类,点进源码中看,有sessionDAO的get,set方法。而sessionDAO则是session会话持久化的关键。

2.创建一个session管理工具类



@Service
public class SessionManagerService {


    @Autowired
    private SecurityManager securityManager;

    @Value("${user.online:true}")
    private boolean online;
    
    /**
    *通过配置控制用户登录的方式,登录方式不同,存入session的用户信息不同
    */
    @Value("${user.login.method:phoneNumber}")
    private String method;

    private DefaultWebSessionManager getSessionManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = (DefaultWebSecurityManager) securityManager;
        DefaultWebSessionManager sessionManager = (DefaultWebSessionManager) defaultWebSecurityManager.getSessionManager();
        return sessionManager;
    }
    
    /**
     * 获取账号对应的 shiro session
     * 1、管理员登录时,传管理员ID
     * 2、用户账号登录时,传用户ID
     * 3、用户手机号码登录时,传手机号码
     * @param id
     * @return
     */
    public Session getSessionByUserIdOr(String id) {
    	if(!online) {
    		return null;
    	}
    	Map<String, Session> sessionsMap = getSessionsMap();
    	return sessionsMap.get(id);
    }
    
    /**
     * 删除会话,强制用户退出,用于一个账号同时只能有一个地方登录
     * 并判断当前登录的session与查出的session是否相同,相同则不删除。
     * (一台电脑上同一个浏览器在 不退出登录直接关闭浏览器时再次登录相同账号session是相同的,这时删除会造成报错)
     * @param session
     */
    public void deleteSession(Session session) {
        if (online && session != null && !SecurityUtils.getSubject().getSession().getId().equals(session.getId())) {
            DefaultWebSessionManager sessionManager = getSessionManager();
            sessionManager.getSessionDAO().delete(session);
        }
    }
    

    /**
     * 获取所有在线用户的session,从session中获取登录用户的信息,与sessionId存与map中
     * @return
     */
    public Map<String, Session> getSessionsMap() {
    	if(!online) {
    		return null;
    	}
    	User user = null;
        Admin admin = null;
        SimplePrincipalCollection principalCollection = null;
        Map<String, Session> userSessionMap = new HashMap<>();
        DefaultWebSessionManager sessionManager = getSessionManager();
        Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();
        for (Session session : sessions) {
            if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) != null) {
                principalCollection = (SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
                if (primaryPrincipal instanceof User) {
                    user = (User) primaryPrincipal;
                    if (method.equals("phoneNumber")) {
                        userSessionMap.put(user.getPhoneNumber(), session);
                    } else {
                        userSessionMap.put(user.getUserId(), session);
                    }
                } else if (primaryPrincipal instanceof Admin) {
                    admin = (Admin) primaryPrincipal;
                    userSessionMap.put(admin.getUserId(), session);
                } else {
                	throw new BizException("不支持的会话类:" + primaryPrincipal.getClass());
                }
            }
        }
        return userSessionMap;
    }

}

1.这里获取sessionManager需要进行两次强转,然后通过sessionManager.getSessionDAO()获取sessionDAO进行session的增删改查。

2.获取到当前所有登录的用户后,再登录接口进行判断,进行登录的用户id是否有对应的session,有则直接删除session,使上一个登录的用户被强制退出。

3.同时获取到登录用户的所有session,查询用户列表时通过判断,是否存在用户Id对应的session从而修改用户是否在线的状态。

3.注意

     1.在大用户量情况下,该方法非常消耗内存,此时不建议使用该方法。

     2.在配置类中我并没有使用注解的方式引用yml文件里配置,原因是注入不进去。注入配置的方法很多种,有兴趣的可以借鉴一下这篇文章。
SpringBoot读取配置文件的六种方式-CSDN博客

··                

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值