SpringBoot进阶之Shiro实现缓存和会话管理功能

491 阅读4分钟

前言

大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个SpringBoot长期系列教程,从入门到进阶, 篇幅会较多~

适合人群

  • 学完Java基础
  • 想通过Java快速构建web应用程序
  • 想学习或了解SpringBoot
  • SpringBoot进阶学习

大佬可以绕过 ~

背景

如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot基础部分,对基本的使用有了初步的认识, 接下来的几期内容将会带大家进阶使用,会先讲解基础中间件的使用和一些场景的应用,或许这些技术你听说过,没看过也没关系,我会带大家一步一步的入门,耐心看完你一定会有收获~

情景回顾

上期带大家学习了Shiro中如何进行权限认证,本期将带大家学习Shiro中如何进行缓存和会话管理,最后我们将做一个在线用户管理以及强制下线用户的功能,同样的,我们集成到Springboot中。

缓存集成

首先我们要明白使用缓存的原因,为啥要用它❓还记得之前带大家实现的用户认证权限认证吗,那里我使用了MockUser,真实场景中是要去数据查询的,这样一来就会产生耗时,请求多的时候数据库肯定忙不过来了,所以我们需要使用缓存来提高程序响应速度

缓存使用Redis,下面就带大家整一下:

<!-- shiro-redis -->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>2.4.2.1-RELEASE</version>
</dependency>

修改ShiroConfig,添加方法

public RedisManager redisManager() {
    RedisManager redisManager = new RedisManager();
    return redisManager;
}

public RedisCacheManager cacheManager() {
    RedisCacheManager redisCacheManager = new RedisCacheManager();
    redisCacheManager.setRedisManager(redisManager());
    return redisCacheManager;
}

@Bean
public SecurityManager securityManager(){
    DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
    securityManager.setRealm(shiroRealm());
    securityManager.setRememberMeManager(rememberMeManager());
    // 添加缓存
    securityManager.setCacheManager(cacheManager());
    return securityManager;
}

这样就可以了,大家可以把测试获取用户的地方改成数据库获取,看下控制台sql日志会明显减少,因为有一部分是从缓存拿的

Session会话管理

这部分功能还是比较好玩的,学完可以自由发挥做一个房间功能,可以加入可以踢人,下面我们就开整

添加SessionDao

修改ShiroConfig,添加方法,因为我们使用的是Redis缓存

@Bean
public RedisSessionDAO sessionDAO() {
    RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
    redisSessionDAO.setRedisManager(redisManager());
    return redisSessionDAO;
}


@Bean
public SessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    Collection<SessionListener> listeners = new ArrayList<SessionListener>();
    listeners.add(new ShiroSessionListener());
    sessionManager.setSessionListeners(listeners);
    sessionManager.setSessionDAO(sessionDAO());
    return sessionManager;
}

实现SessionListener

public class ShiroSessionListener implements SessionListener {
    // 原子值,可以维护用户数
    private final AtomicInteger sessionCount = new AtomicInteger(0);

    // 建立时
    @Override
    public void onStart(Session session) {
        sessionCount.incrementAndGet();
    }

    // 停止时
    @Override
    public void onStop(Session session) {
        sessionCount.decrementAndGet();
    }

    // 过期时
    @Override
    public void onExpiration(Session session) {
        sessionCount.decrementAndGet();
    }
}

最后同样的,想要开启需要我们注入到Manager中:

@Bean
public SecurityManager securityManager(){
    DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
    securityManager.setRealm(shiroRealm());
    securityManager.setRememberMeManager(rememberMeManager());
    securityManager.setCacheManager(cacheManager());
    // 注入session manager
    securityManager.setSessionManager(sessionManager());
    return securityManager;
}

获取在线用户

我们先定义一个类,用来记录在线用户:

@Data
public class UserOnline implements Serializable {

    private static final long serialVersionUID = 3828664348416633856L;

    private String sessionId;

    private String userId;

    private String username;

    private String host;

    private String ip;

    private String status;

    private Date startTime;

    private Date lastTime;

    private Long timeout;
}

那么怎么获取呢?我们定义一个方法,大家实践中可以抽到Service层,这里方便演示,我直接写到控制器里

@RestController
public class IndexController {

    @Autowired
    private SessionDAO sessionDAO;

    @RequiresPermissions("p:user")
    @RequestMapping("/index")
    public String index(Model model) {
        // 登录成后,即可通过Subject获取登录的用户信息
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("user", user);
        return "index --->" + user.getUsername();
    }

    @RequiresPermissions("p:admin")
    @RequestMapping("/userOnline/list")
    @ResponseBody
    public List<UserOnline> list() {
        List<UserOnline> list = new ArrayList<>();
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        for (Session session : sessions) {
            UserOnline userOnline = new UserOnline();
            User user = new User();
            SimplePrincipalCollection principalCollection;
            if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
                continue;
            } else {
                principalCollection = (SimplePrincipalCollection) session
                        .getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                user = (User) principalCollection.getPrimaryPrincipal();
                userOnline.setUsername(user.getUsername());
                userOnline.setUserId(user.getId().toString());
            }
            userOnline.setSessionId((String) session.getId());
            userOnline.setHost(session.getHost());
            userOnline.setStartTime(session.getStartTimestamp());
            userOnline.setLastTime(session.getLastAccessTime());
            Long timeout = session.getTimeout();
            if (timeout == 0l) {
                userOnline.setStatus("离线");
            } else {
                userOnline.setStatus("在线");
            }
            userOnline.setTimeout(timeout);
            list.add(userOnline);
        }
        return list;
    }
    
}

强制用户下线

如果你看谁不爽,可以直接让他下线,hhh~

@RequiresPermissions("p:admin")
@RequestMapping("/forceLogout")
@ResponseBody
public boolean forceLogout(String sessionId) {
    Session session = sessionDAO.readSession(sessionId);
    session.setTimeout(0);
    return true;
}

是不是很简单,这里就不演示了,大家自行试试

结束语

本期内容就到这里结束了,总结一下,本节主要讲了Shiro如何进行缓存以及如何进行用户会话管理,大家可以举一反三,做一些小功能尝试尝试

下期预告

下期给大家讲讲Shiro中如何整合JWT,这个大家应该不陌生,如果不知道啥是JWT也没关系,我会带大家一步一步入门,下期也是Shiro系列的终极篇,内容可能有点多,耐心看完哦。欢迎加群一起学习交流 ~

往期内容

项目源码(源码已更新 欢迎star⭐️)