Spring Security 3.1.8迁移至5.7.9:登录成功但仍为匿名用户
问题描述与解决方案
环境迁移背景
- 原环境:JBossEAP 7.3 + Java8,Spring 3.1.1 + Spring Security 3.1.8
- 目标环境:WildFly26 + Java11,Spring 5.3.29 + Spring Security 5.7.9,XML配置Spring上下文
问题现象
应用启动无报错,但登录成功跳转至default-target-url="/loginSuccessful.htm"时显示匿名用户。调试发现:
- 已注释
/loginSuccessful.htm的isFullyAuthenticated拦截(否则会被重定向回登录页) - 进入该页面前的
SecurityContextHolderFilter中securityContext为null
调试细节
- POST请求
/j_spring_security_check认证成功,权限获取正确 AbstractAuthenticationProcessingFilter的successfulAuthentication方法通过securityContextRepository.saveContext将含UsernamePasswordAuthenticationToken的上下文存入SPRING_SECURITY_CONTEXT属性- 跳转至
/loginSuccessful.htm前,SecurityContextHolderFilter中securityContextRepository.loadContext(request).get()返回null,无法读取上下文 - 已配置:开启
security-context-explicit-save,使用默认HttpSessionSecurityContextRepository,禁用headers和csrf
解决方案:自定义UsernamePasswordAuthenticationFilter替代标签
实现步骤
- 自定义过滤器继承
UsernamePasswordAuthenticationFilter,重写successfulAuthentication方法,强制将SecurityContext同步到Session - 在XML配置中移除
<security:form-login>,替换为自定义过滤器的bean配置
代码示例
自定义过滤器类
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { // 执行父类基础认证逻辑 super.successfulAuthentication(request, response, chain, authResult); // 手动将SecurityContext存入Session,确保上下文持久化 SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(authResult); HttpSession session = request.getSession(false); if (session != null) { session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); } } }
修改SecurityContext.xml配置
替换原<security:form-login>部分,添加自定义过滤器配置:
<!-- 移除原<security:form-login>标签 --> <security:custom-filter ref="customUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER"/> <bean id="customUsernamePasswordAuthenticationFilter" class="my.class.path.security.CustomUsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="loginProcessingUrl" value="/j_spring_security_check"/> <property name="usernameParameter" value="j_username"/> <property name="passwordParameter" value="j_password"/> <property name="authenticationSuccessHandler"> <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler"> <property name="defaultTargetUrl" value="/loginSuccessful.htm"/> <property name="alwaysUseDefaultTargetUrl" value="false"/> </bean> </property> <property name="authenticationFailureHandler"> <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <property name="defaultFailureUrl" value="/loginFailed.htm"/> </bean> </property> <property name="authenticationDetailsSource" ref="customAuthenticationDetailsSource"/> </bean>
完整相关代码与日志
SecurityContext.xml(完整配置)
<security:http auto-config="true" use-expressions="true" disable-url-rewriting="true" security-context-explicit-save="true" security-context-repository-ref="securityContextRepository" > <security:intercept-url pattern="/login.**" access="permitAll" /> <!-- <security:intercept-url pattern="/loginSuccessful.htm" access="isFullyAuthenticated()" /> --> <security:headers disabled="true"/> <security:csrf disabled="true"/> <security:custom-filter ref="customUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER"/> <security:logout invalidate-session="true" logout-success-url="/logoutSuccessful.htm" delete-cookies="JSESSIONID" /> <security:session-management> <security:concurrency-control max-sessions="1" /> </security:session-management> <security:access-denied-handler error-page="/accessDenied.htm" /> </security:http> <security:debug/> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref='ldapAuthProvider' /> </security:authentication-manager> <bean id="securityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" /> <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> <constructor-arg value="${ldap.ldapUrl}" /> </bean> <bean id="customAuthenticationDetailsSource" class="my.class.path.security.CustomAuthenticationDetailsSource"/> <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <bean class="my.class.path.web.security.CustomLdapBindAuthenticator"> <constructor-arg ref="contextSource" /> <property name="userDnPatterns"> <list> <value>cn={0},${ldap.fullyQualifiedDN}</value> </list> </property> </bean> </constructor-arg> <constructor-arg> <bean class="my.class.path.security.CustomLdapAuthoritiesPopulator" /> </constructor-arg> </bean> <bean id="customUsernamePasswordAuthenticationFilter" class="my.class.path.security.CustomUsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="loginProcessingUrl" value="/j_spring_security_check"/> <property name="usernameParameter" value="j_username"/> <property name="passwordParameter" value="j_password"/> <property name="authenticationSuccessHandler"> <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler"> <property name="defaultTargetUrl" value="/loginSuccessful.htm"/> <property name="alwaysUseDefaultTargetUrl" value="false"/> </bean> </property> <property name="authenticationFailureHandler"> <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <property name="defaultFailureUrl" value="/loginFailed.htm"/> </bean> </property> <property name="authenticationDetailsSource" ref="customAuthenticationDetailsSource"/> </bean>
LoginController.java
@Controller public class LoginController { @RequestMapping(value="/login.htm", method = RequestMethod.GET) public String showLogin(Model model,HttpSession httpSession) { httpSession.invalidate(); Boolean fromLogin = true; model.addAttribute("fromLogin", fromLogin); return "login"; } @RequestMapping(value="/loginSuccessful.htm", method = RequestMethod.GET) public ModelAndView loginAccess(Model model, HttpServletRequest request, HttpServletResponse response, Locale locale, Authentication authen, Principal princ) throws NoSuchMessageException, Exception { HttpSessionSecurityContextRepository secRepo = new HttpSessionSecurityContextRepository(); Authentication auth = secRepo.loadContext(request).get().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication3 = SecurityContextHolder.getContextHolderStrategy().getContext().getAuthentication(); // 后续业务逻辑... return new ModelAndView("loginSuccessful"); } }
过滤器注册日志
[INFO] 2023-07-20 16:43:21,906 HttpSecurityBeanDefinitionParser.checkFilterChainOrder:283 -> Checking sorted filter chain: [ Root bean: class [org.springframework.security.web.session.DisableEncodeUrlFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 100, Root bean: class [org.springframework.security.web.session.ForceEagerSessionCreationFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 200, Root bean: class [org.springframework.security.web.context.SecurityContextHolderFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 400, Root bean: class [org.springframework.security.web.session.ConcurrentSessionFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 500, Root bean: class [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 600, Root bean: class [org.springframework.security.web.authentication.logout.LogoutFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 1300, <my.class.path.security.CustomUsernamePasswordAuthenticationFilter#0>, order = 2100, Root bean: class [org.springframework.security.web.authentication.www.BasicAuthenticationFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 2700, Root bean: class [org.springframework.security.web.savedrequest.RequestCacheAwareFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 2800, Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.security.config.http.HttpConfigurationBuilder$SecurityContextHolderAwareRequestFilterBeanFactory#0; factoryMethodName=getBean; initMethodName=null; destroyMethodName=null, order = 2900, Root bean: class [org.springframework.security.web.authentication.AnonymousAuthenticationFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 3200, Root bean: class [org.springframework.security.web.session.SessionManagementFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 3500, Root bean: class [org.springframework.security.web.access.ExceptionTranslationFilter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, order = 3600, <org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0>, order = 3700]
安全过滤器链日志
Security filter chain: [ DisableEncodeUrlFilter ForceEagerSessionCreationFilter SecurityContextHolderFilter ConcurrentSessionFilter WebAsyncManagerIntegrationFilter LogoutFilter CustomUsernamePasswordAuthenticationFilter BasicAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor ]
内容的提问来源于stack exchange,提问作者fl4l
相关产品推荐
相关产品推荐

