IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 7.Spring security中的会话管理 -> 正文阅读

[Java知识库]7.Spring security中的会话管理

会话管理

7.1会话简介

当用户通过浏览器登录成功之后,用户和系统之间就会保持一个会话(session),通过这个会话,系统可以确定出访问用户的身份。Spring security中和会话相关的功能由SessionManagementFilterSessionAuthenticationStrategy接口的组合来处理,过滤器委托该接口对会话进行处理,比较典型的用法有防止会话固定攻击、配置会话并发数等。

7.2会话并发管理

7.2.1实战

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("javaboy")
                .password("{noop}123")
                .roles("admin");
    }

     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable()
                // 开启会话配置
                .sessionManagement()
                // 设置会话并发数为1,类似于挤下线的效果
                .maximumSessions(1)
                // 自定义会话销毁后的行为,重定向到/login页面
                // .expiredUrl("/login")
                // 如果是前后端分离的项目,就不需要页面跳转了,直接返回一段JSON提示即可
                .expiredSessionStrategy(event -> {
                    HttpServletResponse response = event.getResponse();
                    response.setContentType("application/json;charset=utf-8");
                    Map<String, Object> result = new HashMap<>();
                    result.put("status", 500);
                    result.put("msg", "当前会话已经失效, 请重新登录");
                    response.getWriter().print(new ObjectMapper().writeValueAsString(result));
                    response.flushBuffer();
                })
                // 禁止后来者无法使用相同的用户登录,直到当前用户主动注销登录
                .maxSessionsPreventsLogin(true);
    }

    /**
     * 提供HttpSessionEventPublisher实例。Spring security中通过一个Map集合来维护当前的HttpSession记录,进而实现
     * 会话的并发管理。当用户登录成功时,就向集合中添加一条HttpSession记录;当会话销毁时,就从集合中移除一条HttpSession
     * 记录。HttpSessionEventPublisher实现了HttpSessionListener接口,可以监听到HttpSession的创建和销毁事件,并将
     * HttpSession的创建/销毁事件发布出去,这样,当有HttpSession销毁时,spring security就可以感知到该事件了
     */
    @Bean
    HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

7.2.2原理分析

SessionInformation

主要用作spring security框架内的会话记录:

public class SessionInformation implements Serializable {
    // 最近一次请求的时间
    private Date lastRequest;

    // 会话对应的主体(用户)
    private final Object principal;

    // 会话id
    private final String sessionId;

    // 会话是否过期
    private boolean expired = false;

    // 更新最近一次请求的时间
    public void refreshLastRequest() {
        this.lastRequest = new Date();
    }
    // 省略getter/setter
}
SessionRegistry

主要用来维护SessionInformation实例。该接口只有一个实现类SessionRegistryImpl

public class SessionRegistryImpl implements SessionRegistry, ApplicationListener<AbstractSessionEvent> {
    // <principal:Object,SessionIdSet>,保存当前登录主体(用户)和sessionId之间的关系,
    // 由于用对象作为key,因此自定义用户类时需要重写equals和hashCode方法
    private final ConcurrentMap<Object, Set<String>> principals;

    // <sessionId:Object,SessionInformation>,保存sessionId和SessionInformation之间的关系
    private final Map<String, SessionInformation> sessionIds;

    public SessionRegistryImpl() {
        this.principals = new ConcurrentHashMap<>();
        this.sessionIds = new ConcurrentHashMap<>();
    }

    public SessionRegistryImpl(ConcurrentMap<Object, Set<String>> principals,
            Map<String, SessionInformation> sessionIds) {
        this.principals = principals;
        this.sessionIds = sessionIds;
    }

    // 重写ApplicationListener接口中的方法,接收HttpSession相关的事件
    @Override
    public void onApplicationEvent(AbstractSessionEvent event) {
        // 如果是SessionDestroyedEvent事件,则移除掉HttpSession的记录
        if (event instanceof SessionDestroyedEvent) {
            SessionDestroyedEvent sessionDestroyedEvent = (SessionDestroyedEvent) event;
            String sessionId = sessionDestroyedEvent.getId();
            removeSessionInformation(sessionId);
        }
        // 如果是SessionIdChangedEvent事件,则更新HttpSession的记录
        else if (event instanceof SessionIdChangedEvent) {
            SessionIdChangedEvent sessionIdChangedEvent = (SessionIdChangedEvent) event;
            String oldSessionId = sessionIdChangedEvent.getOldSessionId();
            if (this.sessionIds.containsKey(oldSessionId)) {
                Object principal = this.sessionIds.get(oldSessionId).getPrincipal();
                removeSessionInformation(oldSessionId);
                registerNewSession(sessionIdChangedEvent.getNewSessionId(), principal);
            }
        }
    }

    // 返回所有的用户登录对象
    @Override
    public List<Object> getAllPrincipals() {
        return new ArrayList<>(this.principals.keySet());
    }

    // 返回某一个用户所对应的所有SessionInformation,第二个参数表示是否包含已经过期的session
    @Override
    public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {
        Set<String> sessionsUsedByPrincipal = this.principals.get(principal);
        if (sessionsUsedByPrincipal == null) {
            return Collections.emptyList();
        }
        List<SessionInformation> list = new ArrayList<>(sessionsUsedByPrincipal.size());
        for (String sessionId : sessionsUsedByPrincipal) {
            SessionInformation sessionInformation = getSessionInformation(sessionId);
            if (sessionInformation == null) {
                continue;
            }
            if (includeExpiredSessions || !sessionInformation.isExpired()) {
                list.add(sessionInformation);
            }
        }
        return list;
    }

    // 根据sessionId获取SessionInformation
    @Override
    public SessionInformation getSessionInformation(String sessionId) {
        return this.sessionIds.get(sessionId);
    }

    @Override
    public void refreshLastRequest(String sessionId) {
        SessionInformation info = getSessionInformation(sessionId);
        if (info != null) {
            info.refreshLastRequest();
        }
    }

    // 会话保存
    @Override
    public void registerNewSession(String sessionId, Object principal) {
        // 如果sessionId存在,则先将其移除
        if (getSessionInformation(sessionId) != null) {
            removeSessionInformation(sessionId);
        }

        this.sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
        // 第一个参数是当前登录主体,第二个参数则进行计算。如果当前登录主体在principals中已经有对应的value,
        // 则在value的基础上继续添加一个sessionId;如果没有对应的value,则新建一个sessionsUsedByPrincipal对象,
        // 然后再将sessionId添加进去
        this.principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
            if (sessionsUsedByPrincipal == null) {
                sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
            }
            sessionsUsedByPrincipal.add(sessionId);
            return sessionsUsedByPrincipal;
        });
    }

    // 会话移除
    @Override
    public void removeSessionInformation(String sessionId) {
        SessionInformation info = getSessionInformation(sessionId);
        if (info == null) {
            return;
        }

        this.sessionIds.remove(sessionId);
        // 移除value中对应的sessionId
        this.principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
            sessionsUsedByPrincipal.remove(sessionId);
            if (sessionsUsedByPrincipal.isEmpty()) {
                // No need to keep object in principals Map anymore
                sessionsUsedByPrincipal = null;
            }
            return sessionsUsedByPrincipal;
        });
    }
}
SessionAuthenticationStrategy

主要在用户登录成功后,对HttpSession进行处理:

public interface SessionAuthenticationStrategy {
    void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response)
            throws SessionAuthenticationException;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhYCkzyz-1646809765498)(en-resource://database/643:1)]

  • CsrfAuthenticationStrategy:和CSRF攻击有关,该类主要负责在身份验证后删除旧的CsrfToken并生成一个新的CsrfToken
  • ConcurrentSessionControlAuthenticationStrategy:该类主要用来处理session并发问题,例如并发数量控制就是通过该类来完成的。
  • RegisterSessionAuthenticationStrategy:该类用于在认证成功后将HttpSession信息记录到SessionRegistry中。
  • CompositeSessionAuthenticationStrategy:这是一个复合策略,它里边维护了一个集合,其中保存了多个不同的SessionAuthenticationStrategy对象,相当于该类代理了多个SessionAuthenticationStrategy对,大部分情况下,在spring security框架中直接使用的也是该类的实例。
  • NullAuthenticatedSessionStrategy:这是一个空的实现,未做任何处理。
  • AbstractSessionFixationProtectionStrategy:处理会话固定攻击的基类。
  • ChangeSessionIdAuthenticationStrategy:通过修改sessionId来防止会话固定攻击。
  • SessionFixationProtectionStrategy:通过创建一个新的会话来防止会话固定攻击。
ConcurrentSessionControlAuthenticationStrategy

在前面的案例中,起主要作用的是ConcurrentSessionControlAuthenticationStrategy,因此先对该类进行重点分析:

@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
        HttpServletResponse response) {
    // 获取session最大并发数,如果值为-1,则没有限制
    int allowedSessions = getMaximumSessionsForThisUser(authentication);
    if (allowedSessions == -1) {
        return;
    }
    // 获取当前用户的所有未失效的SessionInformation实例
    List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
    
    // 如果获取到的SessionInformation实例数小于当前项目允许的最大session数,说明当前登录没问题,直接返回即可
    int sessionCount = sessions.size();
    if (sessionCount < allowedSessions) {
        return;
    }
    if (sessionCount == allowedSessions) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 仅当此请求与已注册的会话之一关联时才允许
            for (SessionInformation si : sessions) {
                if (si.getSessionId().equals(session.getId())) {
                    return;
                }
            }
        }
    }

	// 如果在前面的判断中没有return,说明当前用户登录的并发数已经超过允许的并发数了,
	// 进入到allowableSessionsExceeded方法中进行处理
    allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
}

protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,
        SessionRegistry registry) throws SessionAuthenticationException {
    // exceptionIfMaximumExceeded的值由maxSeesionsPreventsLogin方法配置,即禁止后来者登录
    if (this.exceptionIfMaximumExceeded || (sessions == null)) {
        throw new SessionAuthenticationException(
                this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
                        new Object[] { allowableSessions }, "Maximum sessions of {0} for this principal exceeded"));
    }

    // 按照最后一次请求的时间进行排序,计算出需要过期的session数量
    sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
    int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
    List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
    for (SessionInformation session : sessionsToBeExpired) {
        session.expireNow();
    }
}
RegisterSessionAuthenticationStrategy

该类的作用主要是向SessionRegistry中记录HttpSession信息:

@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
        HttpServletResponse response) {
    // 调用registerNewSession方法向sessionRegistry中添加一条登录会话信息
    this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
}
CompositeSessionAuthenticationStrategy

相当于一个代理类,默认使用的其实就是该类的实例:

@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
        HttpServletResponse response) throws SessionAuthenticationException {
    // 遍历维护的SessionAuthenticationStrategy集合,然后分别调用其onAuthentication方法
    for (SessionAuthenticationStrategy delegate : this.delegateStrategies) {
        delegate.onAuthentication(authentication, request, response);
    }
}
SessionManagementFilter

和会话并发管理相关的过滤器主要有两个,先来看第一个SessionManagementFilter
其主要用来处理remember-me登录时的会话管理:即如果用户使用了remember-me的方式进行认证,则认证成功后需要进行会话管理,相关的管理操作通过SessionManagementFilter过滤器触发:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    if (request.getAttribute(FILTER_APPLIED) != null) {
        chain.doFilter(request, response);
        return;
    }
    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    // 判断当前会话中是否存在SPRING_SECURITY_CONTEXT_KEY变量。如果是正常的认证流程,则SPRING_SECURITY_CONTEXT_KEY变量
    //是存在于当前会话中的。只有当用户使用了remember-me或者匿名访问某一个接口时,该变量才会不存在
    if (!this.securityContextRepository.containsContext(request)) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // 如果是通过remember-me的方式进行登录,则SecurityContextHolder中获取到的当前用户实例是RememberMeAuthenticationToken,
        // 因此调用SessionAuthenticationStrategy中的onAuthentication方法进行会话管理
        if (authentication != null && !this.trustResolver.isAnonymous(authentication)) {
            try {
                this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
            }
            catch (SessionAuthenticationException ex) {
                SecurityContextHolder.clearContext();
                this.failureHandler.onAuthenticationFailure(request, response, ex);
                return;
            }

            this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
        }
        // 如果是匿名登录,则当前用户实例是AnonymousAuthenticationToken,因此进行会话失效处理
        else {
            if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
                if (this.invalidSessionStrategy != null) {
                    this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
                    return;
                }
            }
        }
    }
    chain.doFilter(request, response);
}
ConcurrentSessionFilter

处理会话并发管理的过滤器。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    HttpSession session = request.getSession(false);
    if (session != null) {
        SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
        if (info != null) {
            if (info.isExpired()) {
                doLogout(request, response);
                // 调用会话过期的回调
                this.sessionInformationExpiredStrategy
                        .onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
                return;
            }
            // 未过期 - 更新最后一次请求时间
            this.sessionRegistry.refreshLastRequest(info.getSessionId());
        }
    }
    chain.doFilter(request, response);
}
Session创建时机

在spring security中,HttpSession的创建策略一共分为四种:

  • ALWAYS:如果HttpSession不存在,就创建。
  • NEVER:从不创建,但是如果已经存在了,则会使用它。
  • IF_REQUIRED:当有需要时,会创建,默认即此。
  • STATELESS:从不创建,也不使用。

需要注意的是,这四种策略仅仅是指spring security中的创建策略,而并非整个应用程序的。第四种适合于无状态的认证方式,意味着服务端不会创建HttpSession,客户端的每一个请求都需要携带认证信息,同时,一些和HttpSession相关的过滤器也将失效,例如SessionManagementFilterConcurrentSessionFilter等。
如果需要的话,可以自行配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
SessionManagementConfigurer

SessionManagementConfigurer配置类完成了上面两个过滤器的配置:

@Override
public void init(H http) {
    SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
    boolean stateless = isStateless();
    // 如果没有获取到SecurityContextRepository的实例,则进行创建,分为两种情况
    if (securityContextRepository == null) {
        // 如果创建策略是STATELESS,则使用NullSecurityContextRepository,相当于不保存
        if (stateless) {
            http.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());
        }
        // 否则构建HttpSessionSecurityContextRepository的实例,并最终存入HttpSecurity的共享对象中以备使用
        else {
            HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
            httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);
            httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
            AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
            if (trustResolver != null) {
                httpSecurityRepository.setTrustResolver(trustResolver);
            }
            http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);
        }
    }
    RequestCache requestCache = http.getSharedObject(RequestCache.class);
    if (requestCache == null) {
        // 如果创建策略是STATELESS,还需要将保存在HttpSecurity共享对象中的请求缓存对象替换为NullRequestCache的实例
        if (stateless) {
            http.setSharedObject(RequestCache.class, new NullRequestCache());
        }
    }
    // 构建三个SessionAuthenticationStrategy的实例,分别是ConcurrentSessionControlAuthenticationStrategy、
    // ChangeSessionIdAuthenticationStrategy、RegisterSessionAuthenticationStrategy,并将这三个实例
    // 由CompositeSessionAuthenticationStrategy进行代理
    http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
    // 构建InvalidSessionStrategy实例
    http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
}

// 主要是构建了两个过滤器SessionManagementFilter和ConcurrentSessionFilter
@Override
public void configure(H http) {
    SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
    // 通过getSessionAuthenticationStrategy方法获取SessionAuthenticationStrategy实例
    // 并传入SessionManagementFilter实例中
    SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository,
            getSessionAuthenticationStrategy(http));
    if (this.sessionAuthenticationErrorUrl != null) {
        sessionManagementFilter.setAuthenticationFailureHandler(
                new SimpleUrlAuthenticationFailureHandler(this.sessionAuthenticationErrorUrl));
    }
    InvalidSessionStrategy strategy = getInvalidSessionStrategy();
    if (strategy != null) {
        sessionManagementFilter.setInvalidSessionStrategy(strategy);
    }
    AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler();
    if (failureHandler != null) {
        sessionManagementFilter.setAuthenticationFailureHandler(failureHandler);
    }
    AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
    if (trustResolver != null) {
        sessionManagementFilter.setTrustResolver(trustResolver);
    }
    sessionManagementFilter = postProcess(sessionManagementFilter);
    http.addFilter(sessionManagementFilter);
    // 如果配置了会话并发控制(只要调用了maximumSessions()方法配置了会话最大并发数,就算开启了会话并发控制),
    // 就再创建一个ConcurrentSessionFilter过滤器并加入HttpSecurity中
    if (isConcurrentSessionControlEnabled()) {
        ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http);

        concurrentSessionFilter = postProcess(concurrentSessionFilter);
        http.addFilter(concurrentSessionFilter);
    }
}
AbstractAuthenticationFilterConfigurer

所以,登录成功后,session并发管理到底是在哪里触发的。虽然经过前面的分析,知道有两个过滤器的存在:SessionManagementFilterConcurrentSessionFilter,但是前者在用户使用rememberMe认证时才会触发session并发管理,后者则根部不会触发session并发管理,这时可以回到AbstractAuthenticationProcessingFilterdoFilter方法中去看一下:

// AbstractAuthenticationProcessingFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    // 省略
    try {
        Authentication authenticationResult = attemptAuthentication(request, response);
        if (authenticationResult == null) {
            // return immediately as subclass has indicated that it hasn't completed
            return;
        }
        // 在这个地方触发了session的并发管理,这里的sessionStrategy对象则是在
        // AbstractAuthenticationFilterConfigurer类的configure方法中进行配置的
        this.sessionStrategy.onAuthentication(authenticationResult, request, response);
        // Authentication success
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        successfulAuthentication(request, response, chain, authenticationResult);
    }
    // 省略
}

// AbstractAuthenticationFilterConfigurer
@Override
public void configure(B http) throws Exception {
    // 省略
    // 从HttpSecurity的共享对象中获取到SessionAuthenticationStrategy实例,
    // 并设置到authFilter过滤器中
    SessionAuthenticationStrategy sessionAuthenticationStrategy = http
            .getSharedObject(SessionAuthenticationStrategy.class);
    if (sessionAuthenticationStrategy != null) {
        this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
    }
    // 省略
}

大致的流程分析:

  • 用户通过用户名/密码发起一个认证请求,当认证成功后,在AbstractAuthenticationProcessingFilter#doFilter方法中触发了session并发管理。
  • 默认的sessionStrategyCompositeSessionAuthenticationStrategy,它一共代理了三个SessionAuthenticationStrategy,分别是ConcurrentSessionControlAuthenticationStrategyChangeSessionIdAuthenticationStrategy以及RegisterSessionAuthenticationStrategy
  • 当前请求在这三个中分别走一圈,第一个用来判断用户的session数是否已经超出限制,如果超出限制就根据配置好的规则作出处理;第二个用来修改sessionId(以防止会话固定攻击);第三个用来将当前session注册到SessionRegistry中。
  • 使用用户名/密码的方式完成认证,将不会涉及ConcurrentSessionFilterSessionManagementFilter两个过滤器。
  • 如果用户使用了remember-me的方式来进行身份认证,则会通过SessionManagementFilter#doFilter方法触发session并发管理。当用户认证成功后,以后的每一次请求都会经过ConcurrentSessionFilter,在该过滤器中,判断当前会话是否已经过期,如果过期就执行注销登录流程;如果没有过期,则更新最近一次请求时间。

7.3会话固定攻击与防御

7.3.1什么是会话固定攻击

会话固定攻击(session fixation attacks)是一种潜在的风险,恶意攻击者有可能通过访问当前应用程序来创建会话,然后诱导用户以相同的会话id登录(通常是将会话id作为参数放在请求链接中,然后诱导用户去单击),进而获取用户的登录身份。例如:

  1. 攻击者自己可以正常访问javaboy网站,在访问的过程中,网站给攻击者分配了一个sessionid。
  2. 攻击者利用自己拿到的sessionid构造一个javaboy网站的链接,并把该链接发送给受害者。
  3. 受害者使用该链接登录javaboy网站(该链接中含有sessionid),登录成功后,一个合法的会话就成功建立了。
  4. 攻击者利用手里的sessionid冒充受害者。

在这个过程中,如果javaboy网站支持URL重写,那么攻击还会变得更加容易。
用户如果在浏览器中禁用了cookie,那么sessionid自然也用不了,所以有的服务端就支持把sessionId放在请求地址中,例如http://www.javaboy.org;jsessionid=xxxxxx。如果服务端支持这种URL重写,那么对于攻击者来说,按照上面的攻击流程,构造一个这样的地址会很容易。

7.3.2会话固定攻击防御策略

Spring security从三个方面入手防范会话固定攻击:

  1. Spring security中默认自带了HTTP防火墙,如果sessionid放在地址栏中,这个请求就会直接被拦截下来。
  2. 在HTTP相应的Set-Cookie字段中有httpOnly属性,这样避免了通过XSS攻击来获取cookie中的会话信息,进而达成会话固定攻击。
  3. 既然会话固定攻击是由于sessionid不变导致的,那么其中一个解决办法就是在用户登录成功后,改变sessionid,spring security中默认实现了该种方案,实现类是ChangeSessionIdAuthenticationStrategy

前两种都是默认行为,第三种方案,spring security中有几种不同的配置策略:

http.sessionManagement().sessionFixation().changeSessionId();

通过sessionFixation()方法开启会话固定攻击防御的配置,一共有四种不同的策略,不同的策略对应了不同的SessionAuthenticationStrategy

  1. changeSessionId():用户登录成功后,直接修改HttpSessionsessionId即可,默认方案即此,对应的处理类是ChangeSessionIdAuthenticationStrategy
  2. none():用户登录成功后,HttpSession不做任何变化,对应的处理类是NullAuthenticatedSessionStrategy
  3. migrateSession():用户登录成功后,创建一个新的HttpSession对象,并将旧的HttpSession中的数据拷贝到新的中,对应的处理类是SessionFixationProtectionStrategy
  4. newSession():用户登录成功后,创建一个新的HttpSession对象,对应的处理类也是SessionFixationProtectionStrategy,只不过将其里边的migrateSessionAttributes属性设置为false。需要注意的是,该方法并非所有的属性都不拷贝,一些spring security使用的属性,如请求缓存,还是会从旧的HttpSession复制到新的HttpSession

7.4Session共享

使用redis进行配置。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-10 22:17:51  更:2022-03-10 22:18:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:37:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码