Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security AuthenticationCredentialsNotFoundException, SecurityContextHolder.getContext is null

I have a strange error, hours of debugging and I can't understand.

UPDATE 1: I use Spring Security 4.0.3, running on Tomcat 7.

The problem is close to this question, maybe the SecurityContextHolder is lost during response.redirect() but the answer doesn't help.

The problem seems close to this question too but the answer has no sense to me.

This is my configuration:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class ProjectSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/login").anonymous();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(Constants.PROFIL_ADMIN).password(Constants.PROFIL_ADMIN).
            roles("ADMIN","TEST_SERVICE");
    }
}

After log in I try to get a secured URL:

@RequestMapping(value = "/myurl", method = RequestMethod.GET)
@ResponseBody
public boolean getTestService(HttpServletRequest request)
        throws SQLException, PoRulesException {

    System.out.println("get security context");
    System.out.println("--------------------");
    SecurityContext secuContext = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
    System.out.println(secuContext);
    System.out.println("get security context holder");
    System.out.println(SecurityContextHolder.getContext());

    return testService.getTestMethod();
}

the method inside the service

@Secured("ROLE_TEST_SERVICE")
public boolean getTestMethod() {
    Sysout.out.println("Hiii")
    return true
}

Now, the log when it doesn't work:

INFO    2016-07-11 15:57:00,006 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Login
INFO    2016-07-11 15:57:00,057 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - User : axel
INFO    2016-07-11 15:57:00,065 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : true
INFO    2016-07-11 15:57:00,065 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_CONSULTER_CA,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context
--------------------
org.springframework.security.core.context.SecurityContextImpl@bbbc4f45: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_CONSULTER_CA,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context holder
org.springframework.security.core.context.SecurityContextImpl@ffffffff: Null authentication
ERROR   2016-07-11 15:57:01,960 [http-bio-8080-exec-10] com.po.exception.GlobalControllerExceptionHandler  - org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:378)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:222)
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
    at com.po.service.CAService$$EnhancerBySpringCGLIB$$f6e21857.getVarAndPrevCa(<generated>)
    at com.po.mvc.controller.CaController.getVarAndPrevCa(CaController.java:56)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:860)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:964)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:515)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:304)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)

and when it works. I just see no difference ... the Spring context is set and contains credential as sysout proove but SecurityContextHolder is not set.

INFO    2016-07-11 16:09:45,374 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Login
INFO    2016-07-11 16:09:45,393 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - User : axel
INFO    2016-07-11 16:09:45,395 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : true
INFO    2016-07-11 16:09:45,395 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_TEST_SERVICE; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context
--------------------
org.springframework.security.core.context.SecurityContextImpl@bbbc4f45: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_TEST_SERVICE Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_CONSULTER_CA, ROLE_USER
get security context holder
org.springframework.security.core.context.SecurityContextImpl@4440cc59: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4440cc59: Principal: org.springframework.security.core.userdetails.User@fc8a: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_CONSULTER_CA,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 19994511EEE72462E8D680CDADCFEC4C; Granted Authorities: ROLE_ADMIN, ROLE_CONSULTER_CA, ROLE_USER

INFO    2016-07-11 16:09:46,879 [http-bio-8080-exec-3] Hiii

The only difference between when it works and when it doesn't is:

  • works when I connect from a page of my website, exemple I'm on mywebsite.com which is public and going to mywebsite.com/login?user=me it works
  • but doesn't work if I come from Google on mywebsite.com/login?user=me

The session property SPRING_SECURITY_CONTEXT is set in both cases but not SecurityContextHolder which fired an exception inside @Secured line 222

I don't want to use a trick like check manually SecurityContextHolder in a method (after redirect) and if is null set the session property SPRING_SECURITY_CONTEXT which is not null, I want fix the issue at the root.

like image 765
amdev Avatar asked Jul 11 '16 14:07

amdev


1 Answers

Assuming you're not able to integrate with your existing SSO provider, the following solution is probably the best you can hope for.

Fundamentally, what you're doing is not going to work unless you limit yourself to a single thread in your container (which is a terrible idea). From the docs on SecurityContextHolder:

Associates a given SecurityContext with the current execution thread.

This is important - the current execution thread. If you make a login request, which is handled by Thread A, then you attempt to go to the secured page, but this request is handled by Thread B, then the SecurityContext you set on A will not be available on B.

Spring Security is quite capable of handling this for you, by storing the security context in the session and storing / fetching it as required (see org.springframework.security.web.session.SessionManagementFilter). You would need make some changes to your configuration, though.

Primarily, you would need to create a new class which extends from AbstractAuthenticationProcessingFilter, and override the attemptAuthentication method. This new filter would then need to be registered, and would need to replace the existing org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter. That class would also be a good place to look to get some idea of how this all hangs together, and what you can configure.

This new filter would do something along the lines of

public Authentication attemptAuthentication(HttpServletRequest request,
    HttpServletResponse response) throws AuthenticationException {
    UserLDAP ldapUser = CorpLDAP.findUser(request);
    User user = new User(ldapUser.getProfil(), ldapUser.getPwd(), Collections.emptyList());
    return new UsernamePasswordAuthenticationToken(user, "", Collections.emptyList());
}

This will return a fully authenticated user, which can be used later on. This is what will be stored in the SecurityContextHolder, and can be easily retrieved. It will also be stored in the session (if configured), so I would recommended making sure it's serializable. Since you don't make use of the password, I would strongly recommend against storing it in the User.

Hopefully that will set you in the right direction.

like image 59
ipsi Avatar answered Oct 04 '22 06:10

ipsi