Spring Shiro 使用默认的Session会话管理

2019-09-22 22:32:20

参考地址 Spring Shiro 使用默认的Session会话管理

项目中用到了shiro session会话管理机制,今天来总结一下,以下都是在spring boot框架实现。

shiro的session管理机制很完善,也是独立于j2ee容器且不依赖的,所以我们完全可以使用shiro提供给我们的session会话管理来实现我们的业务逻辑,默认提供的sessionDAO是memorySessionDAO,这里也主要讲它的配置和原理。

首先来看下如何在spring boot下配置默认的session会话管理:

ShiroConfig类:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@Configuration
public class ShiroConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
 
    @Value("${spring.cache.type}")
    private String cacheType ;
 
    @Value("${server.session-timeout}")
    private int tomcatTimeout;
 
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
 
 
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(userRealm());
        // 自定义缓存实现 使用redis
        if (Constant.CACHE_TYPE_REDIS.equals(cacheType)) {
            securityManager.setCacheManager(rediscacheManager());
        else {
            securityManager.setCacheManager(ehCacheManager());
        }
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }
 
    @Bean
    UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        return userRealm;
    }
 
 
    @Bean
    public SessionDAO sessionDAO() {
            return new MemorySessionDAO();//使用默认的MemorySessionDAO
    }
 
    /**
     * shiro session的管理
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {  //配置默认的sesssion管理器
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(tomcatTimeout * 1000);
        sessionManager.setSessionDAO(sessionDAO());
        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        listeners.add(new BDSessionListener());
        sessionManager.setSessionListeners(listeners);
        return sessionManager;
    }
 
    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager em = new EhCacheManager();
        em.setCacheManager(cacheManager());
        return em;
    }
 
    @Bean("cacheManager2")
    CacheManager cacheManager(){
        return CacheManager.create();
    }
 
 
}

  

DefaultWebSessionManager是我们配置管理器的核心类,里面配置了session的超时时间,sessionDAO和监听器。

在上面我们向spring boot注入了

1
2
3
4
@Bean
    public SessionDAO sessionDAO() {
            return new MemorySessionDAO();//使用默认的MemorySessionDAO
    }

 

 MemorySessionDAO是shiro已经实现的Session的CRUD实现类,包括对session的操作,增删改读。

  下面来看下MemorySessionDAO的源码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class MemorySessionDAO extends AbstractSessionDAO {
    private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);
    private ConcurrentMap<Serializable, Session> sessions = new ConcurrentHashMap();
 
    public MemorySessionDAO() {
    }
 
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        this.storeSession(sessionId, session);
        return sessionId;
    }
 
    protected Session storeSession(Serializable id, Session session) {
        if (id == null) {
            throw new NullPointerException("id argument cannot be null.");
        else {
            return (Session)this.sessions.putIfAbsent(id, session);
        }
    }
 
    protected Session doReadSession(Serializable sessionId) {
        return (Session)this.sessions.get(sessionId);
    }
 
    public void update(Session session) throws UnknownSessionException {
        this.storeSession(session.getId(), session);
    }
 
    public void delete(Session session) {
        if (session == null) {
            throw new NullPointerException("session argument cannot be null.");
        else {
            Serializable id = session.getId();
            if (id != null) {
                this.sessions.remove(id);
            }
 
        }
    }
 
    public Collection<Session> getActiveSessions() {
        Collection<Session> values = this.sessions.values();
        return (Collection)(CollectionUtils.isEmpty(values) ? Collections.emptySet() : Collections.unmodifiableCollection(values));
    }
}

  在源码中我们能看到MemorySessionDAO继承了AbstractSessionDAO类,这是一个抽象类,里面实现了一部分方法,而AbstractSessionDAO又实现了SessionDAO接口,这才是顶级接口,里面提供了5个方法:

 

1
2
3
4
5
6
7
8
9
10
11
12
public interface SessionDAO {
    Serializable create(Session var1);
 
    Session readSession(Serializable var1) throws UnknownSessionException;
 
    void update(Session var1) throws UnknownSessionException;
 
    void delete(Session var1);
 
    Collection<Session> getActiveSessions();
}

  

这5个方法也是memorySessionDAO实现的5个方法。 

 可以看出配置非常简单,我们几乎不需要做任何事情,下面来看看怎么来使用吧,

 以一个SessionService为例:

1
2
3
4
5
6
@Service
public interface SessionService {
    List<UserOnline> list(); //得到系统在线用户
 
    boolean forceLogout(String sessionId); //强制登出
}

  

里面有两个方法,分别是查询系统当前在线的用户,以及强制登出某个用户。

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Service
public class SessionServiceImpl implements SessionService {
    private final SessionDAO sessionDAO;
 
    @Autowired
    public SessionServiceImpl(SessionDAO sessionDAO) {
        this.sessionDAO = sessionDAO;
    }
 
    @Override
    public List<UserOnline> list() {
        List<UserOnline> list = new ArrayList<>();
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        for (Session session : sessions) {
            UserOnline userOnline = new UserOnline();
            if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
                continue;
            else {
                SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) session
                        .getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                UserDO userDO = (UserDO) principalCollection.getPrimaryPrincipal();
                userOnline.setUsername(userDO.getUsername());
            }
            userOnline.setId((String) session.getId());
            userOnline.setHost(session.getHost());
            userOnline.setStartTimestamp(session.getStartTimestamp());
            userOnline.setLastAccessTime(session.getLastAccessTime());
            userOnline.setTimeout(session.getTimeout());
            list.add(userOnline);
        }
        return list;
    }
 
 
    @Override
    public boolean forceLogout(String sessionId) {
        Session session = sessionDAO.readSession(sessionId);
        session.setTimeout(0);
        return true;
    }
}

  

在代码中,我们注入了shiro的SessionDAO,

 Collection<Session> sessions = sessionDAO.getActiveSessions();  可以得到当前在线的session集合。

 每一个session都封装了用户的各种信息,

 userOnline.setId((String) session.getId()); //得到session的ID
 userOnline.setHost(session.getHost()); //得到用户的主机
 userOnline.setStartTimestamp(session.getStartTimestamp()); //得到用户的登录时间
 userOnline.setLastAccessTime(session.getLastAccessTime());//得到用户最后登录时间
 userOnline.setTimeout(session.getTimeout());/得到过期时间

 如果我们想在强制让某用户登出系统,只需要session.setTimeout(0)即可。

 是不是觉得非常简便,如果系统需求不高,我们完全可以使用shiro为我们实现的这个sessionDAO,

而如果系统有定制需求,我们也可以实现自定义的sessionDAO,比如我们可以将session的CRUD通过redis来实现,同样的继承AbstractSessionDAO类,重写它的方法即可,将session的各种存取修改以redis为载体,基本的逻辑就是这样,如果有问题欢迎在评论中留言。


  • 2021-02-03 17:17:53

    iOS 同一个工程下打包不同的app

    应用图标,启动画面,应用启动后的首页都不一样。 有些课程(例如公务员考试和高考)是有目标考试的概念,不同的目标考试大纲是不一样的。拿高考来举例,北京的高考和上海的高考,就有着完全不一样的考试大纲。高考的文科和理科,又有着完全不同的考试科目。 有些课程会有一些自定义的界面,例如高考的应用可以设置昵称,有些课程的真题练习中是有推荐真题模块的,而有些课程又没有。 有些课程有扫描答题卡功能,有些课程有考前冲刺功能,有些课程有大题专项查看功能,而有些课程又没有上述功能。另外还有一些微小细节,但是解决方法和类似,所以就不一一展开说明。

  • 2021-02-04 14:02:30

    window软件界面找不到了跑到屏幕外面去了

    一般可以这样操作,按Alt+空格,然后按M,然后用上下左右键把窗口移动到能看到的地方,再按回车。有些第三方的软件可能不能用,大部分都可以这样做。

  • 2021-02-04 14:08:13

    基于 Electron 的爬虫框架 Nightmare

    Electron 可以让你使用纯 JavaScript 调用 Chrome 丰富的原生的接口来创造桌面应用。你可以把它看作一个专注于桌面应用的 Node.js 的变体,而不是 Web 服务器。其基于浏览器的应用方式可以极方便的做各种响应式的交互,接下来介绍下关于 Electron 上衍生出的框架 Nightmare

  • 2021-02-04 20:13:02

    iOS framework制作及使用(包含xib和图片)

    静态库与动态库简介: 静态库:链接使用时完整地拷贝至可执行文件中 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次 本文制作framework对应xcode版本:10.1

  • 2021-02-11 15:53:08

    node缓存框架memory-cache

    无论是在 desktop, mobile or web哪一方面,Cache都常被我们用来提升程序性能。当处理web应用程序的时候,虽然可以使用当前所有浏览器都支持的响应头来进行客户端缓存,从而提升页面加载效率。但当一个内容非常繁杂的页面需要2s来进行HTML输出的时候,即使启用客户端缓存该页面,服务器仍然需要针对每一个来访用户进行页面渲染。想想一个大型的新闻门户网站首页,难道他们要针对每一个用户一遍又一遍地处理HTML吗?

  • 2021-02-19 16:46:35

    window安装composer

    Composer 是 PHP 的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。