Hybris集成SSO后Spring Security过滤器致用户匿名问题求助
在集成了Spring Security的Hybris 2205.24版本应用中实现SSO时,遇到以下问题:
自定义的SSO过滤器(ssoConnectFilter)配置在springSecurityFilterChain之前执行,过滤器代码如下:
@Override protected void doFilterInternal(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final FilterChain filterChain) throws ServletException, IOException { final boolean isNotConnected = userService.isConnected(userService.getCurrentUser()); String token = httpServletRequest.getParameter("token"); if (isNotConnected && StringUtils.isNotEmpty(token)) { try { final DecodedJWT jwt = verify(token); String id = decodedJWT.getSubject(); helper.ssoLogin(httpServletRequest, httpServletResponse, id); } catch (Exception e) { // log exception } } filterChain.doFilter(httpServletRequest, httpServletResponse); }
在该过滤器执行完毕时,用户已成功认证,执行SecurityContextHolder.getContext().getAuthentication()返回:
Authentication [Principal=sso.test@stackoverflow.com, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_CUSTOMERGROUP]]
但请求经过springSecurityFilterChain后,用户身份变为匿名,此时执行上述代码返回:
AnonymousAuthenticationToken [Principal=anonymous, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=C55080B50140AB87920815D195F4A3E1], Granted Authorities=[ROLE_ANONYMOUS]]
当前过滤器顺序配置:
<ref bean="ssoConnectFilter" /> <ref bean="springSecurityFilterChain" /> <ref bean="otherFilter" />
helper.ssoLogin中的认证逻辑代码:
@Override public void login(final User usr, final HttpServletRequest request, final HttpServletResponse response) { try { final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(usr.getUid(), usr.getPasswordEncoding()); // Set the current user in the user service userService.setCurrentUser(usr); // Define user roles and authorities final ArrayList<GrantedAuthority> auth = new ArrayList<>(); auth.add(new SimpleGrantedAuthority("ROLE_" + Constants.USER.CUSTOMER_USERGROUP.toUpperCase())); // Create an authentication object final Authentication authentication = new GigyaAuthentication(usr, auth); // Apply authentication-related security measures fixation.onAuthentication(authentication, request, response); // Store authentication in the SecurityContext SecurityContextHolder.getContext().setAuthentication(authentication); // Trigger login success processes customerFacade.loginSuccess(); // Set session-related cookies guidCookieStrategy.setCookie(request, response); sessionTokenCookieGenerator.addCookie(response, chanelSessionTokenUtil.generateSessionToken()); // Handle the remember-me cookie mechanism handleRememberMeCookie(request, response); rememberMeServices.loginSuccess(request, response, token); } catch (final Exception e) { // NOSONAR // Clear authentication in case of failure SecurityContextHolder.getContext().setAuthentication(null); } }
需求是在Spring Security过滤器前完成用户认证,确保后续请求能访问需授权页面。
- SecurityContext未持久化到Session:默认情况下,Spring Security的
SecurityContextPersistenceFilter会从HttpSession加载SecurityContext,若自定义过滤器仅将Authentication存入线程局部的SecurityContextHolder,未写入Session,后续springSecurityFilterChain执行时会创建空的SecurityContext,导致匿名用户。 - 自定义Authentication未序列化:若
GigyaAuthentication未实现Serializable接口,无法存入HttpSession,导致后续请求无法加载认证信息。 - 过滤器位置不合理:将自定义SSO过滤器放在
springSecurityFilterChain外部,Spring Security内部的过滤器链可能重置SecurityContext。 - Remember-Me服务参数错误:调用
rememberMeServices.loginSuccess时传入的是UsernamePasswordAuthenticationToken,而非实际生效的GigyaAuthentication,导致Remember-Me机制无法正确关联认证信息。
1. 确保自定义Authentication实现Serializable
修改GigyaAuthentication类,使其实现Serializable接口,保证能存入HttpSession:
public class GigyaAuthentication extends AbstractAuthenticationToken implements Serializable { // 原有业务逻辑代码 }
2. 将SecurityContext持久化到HttpSession
在ssoLogin方法中,设置完Authentication后,手动将SecurityContext写入Session:
// 存储authentication到SecurityContext后添加以下代码 HttpSession session = request.getSession(true); // 获取或创建Session SecurityContext securityContext = SecurityContextHolder.getContext(); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);
或使用Spring Security提供的SecurityContextRepository来完成:
SecurityContextRepository repository = new HttpSessionSecurityContextRepository(); repository.saveContext(SecurityContextHolder.getContext(), request, response);
3. 将自定义SSO过滤器整合到Spring Security过滤器链内部
将ssoConnectFilter添加到Spring Security过滤器链中,放在SecurityContextPersistenceFilter之前执行,确保设置的SecurityContext被后续过滤器正确识别。
Java配置方式
在Hybris的Spring Security配置类中:
@Override protected void configure(HttpSecurity http) throws Exception { // 将自定义SSO过滤器放在SecurityContextPersistenceFilter之前 http.addFilterBefore(ssoConnectFilter, SecurityContextPersistenceFilter.class); // 保留原有Spring Security配置 // ... }
XML配置方式
在Hybris的security配置XML中:
<security:http> <!-- 自定义过滤器放在SECURITY_CONTEXT_FILTER之前 --> <security:custom-filter ref="ssoConnectFilter" before="SECURITY_CONTEXT_FILTER"/> <!-- 其他原有配置 --> </security:http>
4. 修正Remember-Me服务的调用参数
将rememberMeServices.loginSuccess的参数替换为实际生效的GigyaAuthentication对象:
// 替换原来的token参数为authentication rememberMeServices.loginSuccess(request, response, authentication);
5. 验证SecurityContextHolder策略(可选)
确保SecurityContextHolder使用可继承的线程局部策略,避免线程切换时丢失上下文:
// 可在应用启动时设置 SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
- 在
ssoLogin方法执行完毕后,检查HttpSession中是否存在SPRING_SECURITY_CONTEXT属性,且包含正确的GigyaAuthentication对象。 - 调试
SecurityContextPersistenceFilter的doFilter方法,确认其从Session中加载到了正确的SecurityContext。 - 经过
springSecurityFilterChain后,再次调用SecurityContextHolder.getContext().getAuthentication(),验证返回的是已认证用户而非匿名用户。
内容的提问来源于stack exchange,提问作者Rodik

