spring security的session管理_springsecurity session管理-程序员宅基地

技术标签: spring security  

1 spring security的session管理

spring security关于认证session的逻辑处理在接口SessionAuthenticationStrategyonAuthentication方法中,先看看这个接口和其实现类。

1.1 SessionAuthenticationStrategy接口

public interface SessionAuthenticationStrategy {

        /**
         * 当一个认证生成之后处理session的逻辑
         *
         */
        void onAuthentication(Authentication authentication, HttpServletRequest request,
                        HttpServletResponse response) throws SessionAuthenticationException;

}

SessionAuthenticationStrategy只是一个接口,这个接口有很多实现类,但是spring security默认使用的是CompositeSessionAuthenticationStrategy,如下图所示:

1.2 CompositeSessionAuthenticationStrategy

直接看CompositeSessionAuthenticationStrategyonAuthentication方法实现:

private final List<SessionAuthenticationStrategy> delegateStrategies;

public void onAuthentication(Authentication authentication,
                        HttpServletRequest request, HttpServletResponse response)
                                        throws SessionAuthenticationException {
                for (SessionAuthenticationStrategy delegate : this.delegateStrategies) {
                        if (this.logger.isDebugEnabled()) {
                                this.logger.debug("Delegating to " + delegate);
                        }
                        delegate.onAuthentication(authentication, request, response);
                }
        }

是不是很熟悉?CompositeSessionAuthenticationStrategy中有一个list集合存储着实现了SessionAuthenticationStrategy的类,然后遍历这个list,让它们去做正在的session逻辑操作。现在的问题是,这个delegateStrategies中到底存储的是那些实现类呢如图所示

1.3 onAuthentication的调用入口

前面说了spring security的认证流程是从AbstractAuthenticationProcessingFilter开始的,而认证相关的流程则是由其子类UsernamePasswordAuthenticationFilter来实现的。AbstractAuthenticationProcessingFilterattemptAuthentication认证成功之后会调用SessionAuthenticationStrategyonAuthentication方法来处理session相关逻辑。直接上代码

//session策略,默认的实现类是CompositeSessionAuthenticationStrategyprivate SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                        throws IOException, ServletException {
                HttpServletRequest request = (HttpServletRequest) req;
                HttpServletResponse response = (HttpServletResponse) res;
                if (!requiresAuthentication(request, response)) {
                        chain.doFilter(request, response);
                        return;
                }
                Authentication authResult;
                try {
                        authResult = attemptAuthentication(request, response);
                        if (authResult == null) {
                                // return immediately as subclass has indicated that it hasn't completed
                                // authentication
                                return;
                        }
                        //session管理的入口
                        sessionStrategy.onAuthentication(authResult, request, response);
                }
                catch (InternalAuthenticationServiceException failed) {
                        logger.error(
                                        "An internal error occurred while trying to authenticate the user.",
                                        failed);
                        unsuccessfulAuthentication(request, response, failed);

                        return;
                }
                catch (AuthenticationException failed) {
                        // Authentication failed
                        unsuccessfulAuthentication(request, response, failed);

                        return;
                }

                // Authentication success
                if (continueChainBeforeSuccessfulAuthentication) {
                        chain.doFilter(request, response);
                }

                successfulAuthentication(request, response, chain, authResult);
        }

2 SessionRegistry

可能有心急的小伙伴上来就想看ConcurrentSessionControlAuthenticationStrategyChangeSessionIdAuthenticationStrategyRegisterSessionAuthenticationStrategy这三个strategy,但是有点抱歉,我们还缺点前置条件,这个条件就是SessionRegistry搞明白SessionRegistry就搞明白了spring security是怎么保存用户对象和session的。

public interface SessionRegistry {
        /**
         * 获取所有的principal
         /
        List<Object> getAllPrincipals();

        /**
         * 获取principal的所有session
         /
        List<SessionInformation> getAllSessions(Object principal,boolean includeExpiredSessions);

        /**
         * 根据sessionId获取session的详细信息
         /
        SessionInformation getSessionInformation(String sessionId);

        /**
         * 根据sessionId刷新session信息
         /
        void refreshLastRequest(String sessionId);

        /**
         * 注册新的session信息
         /
        void registerNewSession(String sessionId, Object principal);

        /**
         * 根据sessionId删除session信息
         /
        void removeSessionInformation(String sessionId);
}

spring security通过SessionRegistryImpl来实现session信息的统一管理,先看这个类的属性:

public class SessionRegistryImpl implements SessionRegistry,
                ApplicationListener<SessionDestroyedEvent> {

        /** <principal:Object,SessionIdSet> */
    //principals的set集合中存储的是sessionId
        private final ConcurrentMap<Object, Set<String>> principals;
        /** <sessionId:Object,SessionInformation> */
        private final Map<String, SessionInformation> sessionIds;
}

有了principals和sessionIds的属性集合,存取操作这里我们就不用看了,最主要看registerNewSession方法和removeSessionInformation方法。但是有一点需要特别注意,principals是一个map,并且它的key是principal,我们自定义的实体类UserDO一定要重写equals和hashcode方法,否则同一个用户同时在不同的地方登录,后登录的用户是挤不掉前面登录的用户的

2.1 registerNewSession方法

registerNewSession方法是一个比较简单的方法,没有什么特别的技巧就是集合的操作

    public void registerNewSession(String sessionId, Object principal) {
                //1.判断sessionId是否存在,如存在则删除
                if (getSessionInformation(sessionId) != null) {
                        removeSessionInformation(sessionId);
                }
                //2.new一个SessionInformation放到sessionIds中
                sessionIds.put(sessionId,
                                new SessionInformation(principal, sessionId, new Date()));
                //3.添加到ConcurrentHashMap中
                principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
                        if (sessionsUsedByPrincipal == null) {
                                sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
                        }
                        sessionsUsedByPrincipal.add(sessionId);

                        if (logger.isTraceEnabled()) {
                                logger.trace("Sessions used by '" + principal + "' : "
                                                + sessionsUsedByPrincipal);
                        }
                        return sessionsUsedByPrincipal;
                });
        }

瞄一眼session详细信息的实体类SessionInformation

2.2 removeSessionInformation方法

    public void removeSessionInformation(String sessionId) {
                //1.更加sessionId获取session详细信息
                SessionInformation info = getSessionInformation(sessionId);
                //如果不存在直接返回
                if (info == null) {
                        return;
                }
                //2.sessionIds删除sessionId
                sessionIds.remove(sessionId);
                //3.principal中存储的是sessionId的set集合,将目标sessionId从set集合中删除
                principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
                        if (logger.isDebugEnabled()) {
                                logger.debug("Removing session " + sessionId
                                                + " from principal's set of registered sessions");
                        }

                        sessionsUsedByPrincipal.remove(sessionId);

                        if (sessionsUsedByPrincipal.isEmpty()) {
                                // No need to keep object in principals Map anymore
                                if (logger.isDebugEnabled()) {
                                        logger.debug("Removing principal " + info.getPrincipal()
                                                        + " from registry");
                                }
                                sessionsUsedByPrincipal = null;
                        }

                        if (logger.isTraceEnabled()) {
                                logger.trace("Sessions used by '" + info.getPrincipal() + "' : "
                                                + sessionsUsedByPrincipal);
                        }
                        return sessionsUsedByPrincipal;
                });
        }

3 spring security的session管理

3.1 ConcurrentSessionControlAuthenticationStrategy

CompositeSessionAuthenticationStrategydelegateStrategies中有3个SessionAuthenticationStrategy实现类,首当其冲的就是ConcurrentSessionControlAuthenticationStrategy顾名思义它应该是session的并发管理

    public void onAuthentication(Authentication authentication,
                        HttpServletRequest request, HttpServletResponse response) {
                //1.获取到用户所有的session信息
                final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
                                authentication.getPrincipal(), false);
                int sessionCount = sessions.size();
                int allowedSessions = getMaximumSessionsForThisUser(authentication);
                //2.比较当前的session的数量和设定的最大允许session数量进行对吧
                if (sessionCount < allowedSessions) {
                        // They haven't got too many login sessions running at present
                        return;
                }
                if (allowedSessions == -1) {
                        // We permit unlimited logins
                        return;
                }
                if (sessionCount == allowedSessions) {
                        HttpSession session = request.getSession(false);
                        if (session != null) {
                                // Only permit it though if this request is associated with one of the
                                // already registered sessions
                                for (SessionInformation si : sessions) {
                                        if (si.getSessionId().equals(session.getId())) {
                                                return;
                                        }
                                }
                        }
                }
                //3.抛异常或者是按session生成的时间进行排序,抛弃掉早先生成的session,保持session数量为允许的size
                allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
        }

3.2 ChangeSessionIdAuthenticationStrategy

ChangeSessionIdAuthenticationStrategy中的onAuthentication方法其实是由父类AbstractSessionFixationProtectionStrategy实现的。这个方法就是防止固定会话攻击,会一直不停的修改你的sessionId

public void onAuthentication(Authentication authentication,
                        HttpServletRequest request, HttpServletResponse response) {
                boolean hadSessionAlready = request.getSession(false) != null;
                //1.session不存在直接返回
                if (!hadSessionAlready && !alwaysCreateSession) {
                        return;
                }
                HttpSession session = request.getSession();
                //2.如果session存在的话则创建一个新的session覆盖老的session,sessionId也会改变
                if (hadSessionAlready && request.isRequestedSessionIdValid()) {
                        String originalSessionId;
                        String newSessionId;
                        Object mutex = WebUtils.getSessionMutex(session);
                        synchronized (mutex) {
                                // We need to migrate to a new session
                                originalSessionId = session.getId();

                                session = applySessionFixation(request);
                                newSessionId = session.getId();
                        }
                        if (originalSessionId.equals(newSessionId)) {
                                logger.warn("Your servlet container did not change the session ID when a new session was created. You will"
                                                + " not be adequately protected against session-fixation attacks");
                        }
                        onSessionChange(originalSessionId, session, authentication);
                }
        }

3.3 RegisterSessionAuthenticationStrategy

RegisterSessionAuthenticationStrategyonAuthentication方法是最简单的,就是注册一个新的sessionId

public void onAuthentication(Authentication authentication,
                        HttpServletRequest request, HttpServletResponse response) {
                sessionRegistry.registerNewSession(request.getSession().getId(),
                                authentication.getPrincipal());
        }

4.session共享

说到底SessionRegistryImpl是基于内存的session管理,我们在开发中经常要用到session共享。那么spring security提供的默认session注册表就不再起作用了,其实SessionRegistry是有2种实现的。看看另外一种实现SpringSessionBackedSessionRegistry

对于session的操作方法上面已经讲过,只不过是存储介质的不同,这里就不再赘述,我们主要关心FindByIndexNameSessionRepository,对于session的所有处理都是由这个类来处理的。


RedisIndexedSessionRepository实现了这个类,而RedisIndexedSessionRepository的实现则由spring-session-data-redis提供,换句话说如果我们想实现session的共享直接依赖这个jar包,然后将session的存储介质改为redis即可。

4 总结

在spring security中sessionId的生成和处理逻辑要经历这三个strategy,sessionId每一次请求都会变化用来防止固定会话攻击。如果对session的最大size做了限制在ConcurrentSessionControlAuthenticationStrategy会有一个淘汰策略永远取最新生成的sessionSessionRegistryImpl是基于内存的session管理,我们在开发中经常要用到session共享,那就依赖spring-session-data-redis即可。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36500178/article/details/113091776

智能推荐

生活垃圾数据集(YOLO版)_垃圾回收数据集-程序员宅基地

文章浏览阅读1.6k次,点赞5次,收藏20次。【有害垃圾】:电池(1 号、2 号、5 号)、过期药品或内包装等;【可回收垃圾】:易拉罐、小号矿泉水瓶;【厨余垃圾】:小土豆、切过的白萝卜、胡萝卜,尺寸为电池大小;【其他垃圾】:瓷片、鹅卵石(小土豆大小)、砖块等。文件结构|----classes.txt # 标签种类|----data-txt\ # 数据集文件集合|----images\ # 数据集图片|----labels\ # yolo标签。_垃圾回收数据集

天气系统3------微服务_cityid=101280803-程序员宅基地

文章浏览阅读272次。之前写到 通过封装的API 已经可以做到使用redis进行缓存天气信息但是这一操作每次都由客户使用时才进行更新 不友好 所以应该自己实现半小时的定时存入redis 使用quartz框架 首先添加依赖build.gradle中// Quartz compile('org.springframework.boot:spring-boot-starter-quartz'..._cityid=101280803

python wxpython 不同Frame 之间的参数传递_wxpython frame.bind-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏8次。对于使用触发事件来反应的按钮传递参数如下:可以通过lambda对function的参数传递:t.Bind(wx.EVT_BUTTON, lambda x, textctrl=t: self.input_fun(event=x, textctrl=textctrl))前提需要self.input_fun(self,event,t):传入参数而同时两个Frame之间的参数传..._wxpython frame.bind

cocos小游戏开发总结-程序员宅基地

文章浏览阅读1.9k次。最近接到一个任务要开发消消乐小游戏,当然首先就想到乐cocosCreator来作为开发工具。开发本身倒没有多少难点。消消乐的开发官网发行的书上有专门讲到。下面主要总结一下开发中遇到的问题以及解决方法屏幕适配由于设计尺寸是750*1336,如果适应高度,则在iphonX下,内容会超出屏幕宽度。按宽适应,iphon4下内容会超出屏幕高度。所以就需要根据屏幕比例来动态设置适配策略。 onLoad..._750*1336

ssm435银行贷款管理系统+vue_vue3重构信贷管理系统-程序员宅基地

文章浏览阅读745次,点赞21次,收藏21次。web项目的框架,通常更简单的数据源。21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对银行贷款管理系统进行了介绍,包括研究的现状,还有涉及的开发背景,然后还对系统的设计目标进行了论述,还有系统的需求,以及整个的设计方案,对系统的设计以及实现,也都论述的比较细致,最后对银行贷款管理系统进行了一些具体测试。_vue3重构信贷管理系统

乌龟棋 题解-程序员宅基地

文章浏览阅读774次。题目描述原题目戳这里小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。乌龟棋的棋盘是一行 NNN 个格子,每个格子上一个分数(非负整数)。棋盘第 111 格是唯一的起点,第 NNN 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。乌龟棋中 MMM 张爬行卡片,分成 444 种不同的类型( MMM 张卡片中不一定包含所有 444 种类型的卡片,见样例),每种类型的卡片上分别标有 1,2,3,41, 2, 3, 41,2,3,4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数

随便推点

python内存泄露的原因_Python服务端内存泄露的处理过程-程序员宅基地

文章浏览阅读1.5k次。吐槽内存泄露 ? 内存暴涨 ? OOM ?首先提一下我自己曾经历过多次内存泄露,到底有几次? 我自己心里悲伤的回想了下,造成线上影响的内存泄露事件有将近5次了,没上线就查出内存暴涨次数可能更多。这次不是最惨,相信也不会是最后的内存的泄露。有人说,内存泄露对于程序员来说,是个好事,也是个坏事。 怎么说? 好事在于,技术又有所长进,经验有所心得…. 毕竟不是所有程序员都写过OOM的服务…. 坏事..._python内存泄露

Sensor (draft)_draft sensor-程序员宅基地

文章浏览阅读747次。1.sensor typeTYPE_ACCELEROMETER=1 TYPE_MAGNETIC_FIELD=2 (what's value mean at x and z axis)TYPE_ORIENTATION=3TYPE_GYROSCOPE=4 TYPE_LIGHT=5(in )TYPE_PRESSURE=6TYPE_TEMPERATURE=7TYPE_PRO_draft sensor

【刘庆源码共享】稀疏线性系统求解算法MGMRES(m) 之 矩阵类定义三(C++)_gmres不构造矩阵-程序员宅基地

文章浏览阅读581次。/* * Copyright (c) 2009 湖南师范大学数计院 一心飞翔项目组 * All Right Reserved * * 文件名:matrix.cpp 定义Point、Node、Matrix类的各个方法 * 摘 要:定义矩阵类,包括矩阵的相关信息和方法 * * 作 者:刘 庆 * 修改日期:2009年7月19日21:15:12 **/

三分钟带你看完HTML5增强的【iframe元素】_iframe allow-top-navigation-程序员宅基地

文章浏览阅读1.7w次,点赞6次,收藏20次。HTML不再推荐页面中使用框架集,因此HTML5删除了&lt;frameset&gt;、&lt;frame&gt;和&lt;noframes&gt;这三个元素。不过HTML5还保留了&lt;iframe&gt;元素,该元素可以在普通的HTML页面中使用,生成一个行内框架,可以直接放在HTML页面的任意位置。除了指定id、class和style之外,还可以指定如下属性:src 指定一个UR..._iframe allow-top-navigation

Java之 Spring Cloud 微服务的链路追踪 Sleuth 和 Zipkin(第三个阶段)【三】【SpringBoot项目实现商品服务器端是调用】-程序员宅基地

文章浏览阅读785次,点赞29次,收藏12次。Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,

烁博科技|浅谈视频安全监控行业发展_2018年8月由于某知名视频监控厂商多款摄像机存在安全漏洞-程序员宅基地

文章浏览阅读358次。“随着天网工程的建设,中国已经建成世界上规模最大的视频监控网,摄像头总 数超过2000万个,成为世界上最安全的国家。视频图像及配套数据已经应用在反恐维稳、治安防控、侦查破案、交通行政管理、服务民生等各行业各领域。烁博科技视频安全核心能力:精准智能数据采集能力:在建设之初即以应用需求为导向,开展点位选择、设备选型等布建工作,实现前端采集设备的精细化部署。随需而动的AI数据挖掘能力:让AI所需要的算力、算法、数据、服务都在应用需求的牵引下实现合理的调度,实现解析能力的最大化。完善的数据治理能力:面_2018年8月由于某知名视频监控厂商多款摄像机存在安全漏洞