I'm migrating a quite old application in Spring 3.1.1 + Spring Security 3.1.8, that works fine in JBossEAP 7.3 + Java8,
to Spring 5.3.29 + Spring Security 5.7.9, executed by WildFly26 + Java11.
I use xml notation for Spring context declaration.
The application starts and the context is loaded fine with no errors, the problem comes after a success login call, I get an Anonymous user at the default-target-url="/loginSuccessful.htm page, due to a null securityContext in the SecurityContextHolderFilter just before that page (in the following configuration see that I commented the "isFullyAuthenticated" intercept-url check to loginSuccessful.htm for debugging, alternatively I've been redirected to login page).
In the migration I enabled the security-context-explicit-save and added HttpSessionSecurityContextRepository as securityContextRepository (that is the default) as requested by documentation and disabled headers and csrf.
From debug I see that the POST to /j_spring_security_check produces an authentication with success and the permissions are retrieved correctly: the AbstractAuthenticationProcessingFilter, in successfulAuthentication performs the context saving by calling this.securityContextRepository.saveContext by passing a UsernamePasswordAuthenticationToken (where this.securityContextRepository is a HttpSessionSecurityContextRepository object saved to SPRING_SECURITY_CONTEXT property name), and finally calls success authentication function.
In the next SecurityContextHolderFilter, just before the /loginSuccessful.htm, the following line generates a null: SecurityContext securityContext = this.securityContextRepository.loadContext(request).get(); its try to retrieve an HttpSessionSecurityContextRepository by getting a property with name SPRING_SECURITY_CONTEXT
I know that there is something wrong in my configutation, but I don't know what, I have read many question similar to mine, in particular this one, or this other, that, or that other and many other, tried with no luck its solutions. Is it needed to implement a custom UsernamePasswordAuthenticationFilter in substitution to <security:form-login /> tag? In what way? I didn't find any examples
Here my code:
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:form-login
login-page="/login.htm"
login-processing-url="/j_spring_security_check"
username-parameter="j_username"
password-parameter="j_password"
authentication-failure-url="/loginFailed.htm"
default-target-url="/loginSuccessful.htm"
authentication-details-source-ref="customAuthenticationDetailsSource"
always-use-default-target="false"
/>
<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>
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();
}
See that I tried to get the Principal and Authentication in many ways, but always null
Here is the filter registration from logs:
[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, <org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#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 from logs:
Security filter chain: [
DisableEncodeUrlFilter
ForceEagerSessionCreationFilter
SecurityContextHolderFilter
ConcurrentSessionFilter
WebAsyncManagerIntegrationFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
All seems to be OK as I see in the debugger, but where is the problem? any help is appreciated