简体   繁体   中英

How to use PreAuthorize with async Spring MVC controller

I have a Spring MVC service which lets me upload a file. It uses @PreAuthorize from Spring Security to handle access controls on resources. The controller uses Servlet 3 async servlets by use of a Callable.

@PreAuthorize("...")
@RequestMapping(value = "upload", method = RequestMethod.PUT)
public Callable<ResponseEntity> upload(final InputStream inputStream)
{
    return new Callable<ResponseEntity>()
    {
        @Override
        public ResponseEntity call() throws Exception
        {
            ...
        }
    };
}

Somewhere in the service - outside of my code - an exception is being thrown.

An Authentication object was not found in the SecurityContext

The exception appears to be thrown by the cglib generated code for my Spring controller. Here is bulk of the stack trace.

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339) ~[spring-security-core-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198) ~[spring-security-core-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631) ~[spring-aop-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at com.testing.upload.controller.RESTService$$EnhancerByCGLIB$$66a0c4c9.upload(<generated>) ~[spring-core-3.2.4.RELEASE.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_65]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_65]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_65]
    at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_65]
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219) ~[spring-web-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132) ~[spring-web-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745) ~[spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686) ~[spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80) ~[spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925) ~[spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856) ~[spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936) [spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:849) [spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:758) [javax.servlet-3.0.0.v201112011016.jar:na]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812) [spring-webmvc-3.2.4.RELEASE.jar:3.2.4.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:848) [javax.servlet-3.0.0.v201112011016.jar:na]

I have updated my web.xml from the instructions at the bottom of this blog post about async support in Spring Security 3.2.

Finally, the error does not always happen. It only appears to happen when I try to upload a large file. This makes me think that it does indeed have something to do with trying to make the controller an async controller.

Versions:

  • Spring 3.2.4
  • Spring Security 3.2
  • cglib 3.1
  • Java Servlet 3.0
  • Java 6
  • Jetty 8

It is possible that the security context is unavailable if the upload is happening in a spawned thread. This is because the security context by default is bound to the parent thread and a new child threads won't know about it.

You can try changing the SecurityContextHolder strategy to InheritableThreadLocal making it available to spawned threads.

You can set this programatically:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL

Or in the Spring XML config:

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass"
        value="org.springframework.security.core.context.SecurityContextHolder"/>
    <property name="targetMethod" value="setStrategyName"/>
    <property name="arguments"><list><value>MODE_INHERITABLETHREADLOCAL</value></list></property>
</bean>

Or you can set an system property when you start your app:

-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM