中信国际inMotion项目Session id重复问题每日进度更新_20250304

中信国际inMotion项目Session id重复问题每日进度更新_20250304

  1. 分析涉及Spring Session​的各个filter源码。

详细排查过程

开源版本问题

根据之前排查的线索,重点排查登录过程中位于SessionRepositoryFilter​以及CommonsRequestResponseLoggingFilter​之间的filter。

CommonsRequestResponseLoggingFilter​中,通过以下代码获取Session

#通过request对象尝试获取会话,如果会话不存在则返回null,不进行强制创建
HttpSession session = request.getSession(false);

这里的request​对象来源于Filter的HttpServletRequest​入参,因此各个Filter​可以通过包装Request​对象实现session获取的自定义过程。

为了保证源码排查的严谨性,会通过两种手段确认排查结果:

  1. 本地源码Debug

  2. UAT​环境使用Arthus​工具对Filter​的入参进行监控。通过检查当前Filter​的下一个Filter​的入参类型确认排查结果。

    watch -b javax.servlet.Filter doFilter "{target.getClass(),params[0].getClass()}"
    

Filter 状态 备注
OrderedFormContentFilter 已排查
OrderedRequestContextFilter 已排查
DelegatingFilterProxy 已排查
WebAsyncManagerIntegrationFilter 已排查
SecurityContextPersistenceFilter 已排查
HeaderWriterFilter 已排查
LogoutFilter 已排查
RequestCacheAwareFilter 已排查
SecurityContextHolderAwareRequestFilter 已排查
AnonymousAuthenticationFilter 已排查
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
ApplyHttpServletRequestWrapperFilter

排查1. OrderedFormContentFilter

核心功能是对表单数据进行处理,特别是针对 application/x-www-form-urlencoded 和 multipart/form-data 类型的请求。​OrderedFormContentFilter​ 会对原始的 HttpServletRequest​ 对象进行包装,生成一个新的请求对象。这个包装后的请求对象会对表单数据进行处理和优化,例如对请求参数进行排序,以确保数据的一致性和可预测性。这样,在后续的请求处理过程中,控制器方法可以直接从包装后的请求对象中获取经过处理的表单数据。

image

这里可以看到在有需要处理的参数时,会使用FormContentRequestWrapper​对请求进行包装。

image

image

登录请求使用的Content-Type为:application/json​,不满足条件,因此不会对Request​进行包装。

  • 本地debug

image

  • UAT

image

排查2. OrderedRequestContextFilter

主要用于管理请求上下文信息,提供RequestContextHolder​的支持。

image

在这个Filter中,未对HttpServletRequest​对象进行包装。其它代码用于提供RequestContextHolder​的支持,主要作用是在整个请求处理过程中保存和获取当前请求的上下文信息,为开发者提供了一种方便的方式来访问当前请求的相关数据,即使在无法直接获取 HttpServletRequest​ 对象的情况下也能做到。

  • UAT

image

排查3. DelegatingFilterProxy

Spring 框架提供的一个特殊的过滤器,它在 Servlet 容器和 Spring 应用上下文之间起到了桥梁的作用。

image

image

这里可以看到请求被委托到另一个过滤器了

通过debug可以看到实际上被委托到:`

imageFilterChainProxy​中,则将Spring Security​中的Filter加入到处理链中并执行,交给VirtualFilterChain​处理。结合之前排查的线索,可以看到额外加入的Filter​与线索中得到的结果是一致的,即额外加入的所有Filter都是属于Spring Security​的。

49ca21e65056f6cd77e8316a659bc4f0

在这个地方,可以看到通过firewall​对request​进行了二次包装成FirewalledRequest​对象。继续跟踪,确认firewall使用的是StrictHttpFirewall​。

image

继续查看getFirewalledRequest​的实现

image

image

这个FirewalledRequest​对原始的请求进行简单封装,并没有对getSession​进行特殊处理。

排查4. WebAsyncManagerIntegrationFilter

主要用于在异步 Web 请求处理场景下集成 WebAsyncManager​ 与 Servlet 容器的异步处理机制,确保异步请求在整个处理流程中上下文信息的正确传递和管理。

c4b67810c5780b1cf628c7b87e116b6f

1965940c5d1f173bbed49395d424835b

这里是将异步管理器与当前的request​进行绑定操作,传递的仍是之前的request​对象。

  • UAT

image

排查5. SecurityContextPersistenceFilter

负责安全上下文(SecurityContext​)的持久化管理。

63f682f040d3eae2b0482984bba0cdd82c24c07515156f58539eb0c22dfa1cc2

首先尝试从repo​中恢复安全上下文,默认情况下使用:HttpSessionSecurityContextRepository​实现。HttpSessionSecurityContextRepository​会将包含用户身份和权限等信息的SecurityContext​存储到HttpSession​中。这样,在同一个会话期间的后续请求都可以从HttpSession​中获取到该安全上下文,从而实现用户身份的持续跟踪和授权检查。

  • loadContext(HttpRequestResponseHolder requestResponseHolder)​:该方法用于从HttpSession​中加载SecurityContext​。它首先从HttpSession​中获取已存储的SecurityContext​,如果不存在,则创建一个新的空SecurityContext​。​

在这个步骤,可以看到对Request​进行了包装成为:SaveToSessionRequestWrapper

39d2d89917df2038fe1923d410594d63

93624ddea5e00e2b20d23ae41e09cf54

该包装是为了处理异步模式下禁用自动保存安全上下文,也并没有对getSession​进行重写。

  • saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response)​:此方法用于将SecurityContext​保存到HttpSession​中。它会将当前的SecurityContext​存储到HttpSession​,以便后续请求可以获取。

b257f594c4a5800f2c7c19910eba4049

  • generateNewContext()​:用于创建一个新的空SecurityContext​,当在HttpSession​中未找到已存储的SecurityContext​时会调用此方法。

排查6. HeaderWriterFilter

用于在 HTTP 响应中添加或修改特定的 HTTP 头信息,以此增强应用程序的安全性和功能性。

5c2e8d8863a593950d854924cf1e8a3b

ffc2ca4e4cfd7b82bef2f8150c9cf1b4

这里的逻辑比较简单,主要就是在请求结束前委托HeaderWriter​将header写入响应中,有不同的实现,例如防止跨站脚本攻击(XSS)、缓存控制等。

这里通过HeaderWriterRequest​对Request​进行了包装,但是并没有对getSession​进行重写。

43832f3be9fa5ca4118e76530374acd7

排查7. LogoutFilter

用于处理用户的注销请求,清理用户的认证信息和会话状态,确保用户安全地退出系统。

de81b3bbc43316757e95d96fdef874a2

这里的功能比较简单,先判断当前是否是logout

42e000a485495256fe85666b12c9601c

默认情况下,判断当前请求路径是否为:/logout​。inmotion​登录的地址为:/directbank/service/v1/login​,不匹配,所以会跳过处理。

排查8. RequestCacheAwareFilter

用于处理用户在进行身份验证过程中被重定向到登录页面后,如何恢复原始请求的功能。

b5d545201966c6d8808945135cc7f21d

Filter​首先尝试从RequestCache​中获取和本次请求相匹配的缓存请求,默认的实现是:HttpSessionRequestCache​,基于Session​的实现。

4d94f769a76c4b555b54d489aa9f87b7

41e006ee78cbadbfad9738aca29ae509

这里实际上就是尝试获取当前session​如果能获取到则获取其中特定的attribute​返回。缓存Request​对象的写入位于ExceptionTranslationFilter​的发送认证请求部分。

9dee90292e83fd72e25356624c6e8663

inmotion​登录场景中并没有重定向的需求,从排查的结果看也没有对Request​进行封装。

  • UAT

image

排查9. SecurityContextHolderAwareRequestFilter

用于将原始的 HttpServletRequest​ 请求对象包装成一个安全感知的请求对象,为后续的请求处理提供更便捷的安全信息访问方式。

21549391529a53126f9c7a9a156cdf7d

通过RequestFactory​对原始请求进行包装,默认的实现为:HttpServlet3RequestFactory​。

8d283d7796075aafb81fc9cfda7d23e9

a22c0e66a1a11c8c6d711a5bcba72c7c

factory​返回一个Servlet3SecurityContextHolderAwareRequestWrapper​对象,该对象主要是提供了一些和登录、注销相关的方法,并未对getSession​进行重写。

排查10. AnonymousAuthenticationFilter

在用户未进行显式身份验证时,为请求创建一个匿名身份,使得后续的安全处理流程能够统一处理已认证用户和未认证用户的请求。

acdd52d4e99ef1032cbdd89b60923b70

如上所述,该filter仅用于创建匿名身份。

排查11. SessionManagementFilter

负责管理用户会话相关的安全问题,确保用户会话的安全性、有效性和一致性。主要提供以下能力:

  1. 会话并发控制
  2. 会话超时管理
  3. 会话固定攻击防护

特征统计

请求特征

URI 文件 1 中出现次数 文件 2 中出现次数 文件 3 中出现次数
/uaa - coc - authorize/v1/devicebinding/pullDeviceMessage 16 22 12
/directbank/service/v1/td/landingInfo 6 6 3
/directbank/securities/v1/checkForEiao 2 3 2
/directbank/service/v1/customer/checkAddress 3 3 2
/uaa - coc - authorize/v1/devicebinding/svGetBindFlag 4 3 2
/directbank/service/v1/td/preparation 7 2 1
/directbank/service/v1/wm/promptAlert 2 3 2
/directbank/customer/v1/newFund 6 3 1
/directbank/service/v1/customer/cddInfoGet 2 9 5
/uaa - coc - authenticate/v1/login 3 3 2
/uaa - coc - authorize/v1/devicebinding/svGetDeviceUserID 4 3 2
/directbank/service/v1/customer/mainpageAuthFlag?type = getPermissions 3 3 2
/directbank/td/v1/rate/HKD?segment = true 3 3 2
/directbank/service/v1/td/opening 2 2 1
/directbank/account/v1/accounts 3 3 2
/directbank/account/v1/balances?realtime = false 3 3 2
/directbank/customer/v1/getBpInfo 3 3 2
/uaa - coc - idp/oauth/token?grant_type = client_credentials&scope = protected%20dbank&client_secret = 6d388860 - ac9c - 42bf - 81b7 - b0094fb1b71a&client_id = 2b439db7 - 496f - 4328 - b98b - 79318dd48cda 3 3 2
/directbank/service/v1/login 3 3 2
/directbank/service/v1/td/rate/HKD?segment = true 3 3 2
/directbank/account/v1/lostCard/getAvlCardList 3 3 2
/directbank/service/v1/getOfferByLocation 3 3 2
/directbank/service/v1/monopoly/checkEligibility 3 3 2
/directbank/service/v1/td/newFund 3 3 2
/directbank/service/v1/account/balanceSumm?acctType = ALL 3 3 2
/directbank/service/v1/ft/remitBindingList 3 3 2
/directbank/account/v1/campaign/registerInfo 3 3 2
/directbank/service/v1/getMyOfferInfo 3 3 2
/directbank/service/v1/customer/changePermission/qurey?type = ADDRESS,EMAIL,MOBILE,REACTIVATE 3 3 2
/directbank/service/v1/getMyTotalAssetInfo 3 3 2
/directbank/account/v1/accounts/TD 2 2 1
/directbank/account/v1/lostCard/getAbleToLostAndActStatus 7 6 4
/uaa - coc - idp/v1/user/login 3 3 2
/directbank/service/v1/account/accounts/additionalInfo 6 6 5
/directbank/monopoly/v1/checkEligibility 3 3 2
/directbank/service/v1/vcc/eligibility/etb 3 3 2
/directbank/td/v1/preparation 7 2 1
/directbank/td/v1/landingInfo 6 6 3
/directbank/service/v1/getMyTotalAssetInfo 3 3 2
/directbank/service/v1/lostCard/getAbleToLostAndActStatus 7 6 4