You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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替代标签

实现步骤

  1. 自定义过滤器继承UsernamePasswordAuthenticationFilter,重写successfulAuthentication方法,强制将SecurityContext同步到Session
  2. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 16:54:58