I have a problem getting an AuthenticationCredentialsNotFoundException . At the moment, I believe this is a threading issue. According to another question ( link ), the SecurityContext is passed through the HttpSession object between different threads, but for some reason this does not work for me.
This is how I handle login at the moment:
public ShopAdminDTO login(String userEmail, String password) throws EmailAddressNotFoundException { LOGGER.debug("Login request for " + userEmail); // Create and initialize user details object for Spring Security authentication mechanism. ShopAdminUserDetails userDetails = new ShopAdminUserDetails(userEmail, password, true, true, true, true, new ArrayList<GrantedAuthority>()); // Create authentication object for the Spring SecurityContext Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, password, new ArrayList<GrantedAuthority>()); boolean requiresEmailActivation = this.shopAdminValidationTokenRepository.getRequiresEmailValidation(userEmail); if(requiresEmailActivation == true) { LOGGER.info("Login denied: Email is not validated yet."); // IMPORTANT NOTE: We throw an EmailNotFoundException instead of a // PleaseValidateYourEmailFirstException in order to NOT reveal // that this email exists. So: Do not "FIX" this! throw new EmailAddressNotFoundException(); } LOGGER.debug("Email appears validated."); try { // Execute authentication chain to try user authentication auth = this.adminAuthenticationProvider.authenticate(auth); } catch(BadCredentialsException e) { // FIXME Login: We could/should count and limit login attempts here? LOGGER.info("Bad credentials found for: " + userEmail); throw e; } LOGGER.info("User successfully authenticated [userEmail="+userEmail+"]"); // Set the authentication to the SecurityContext, the user is now logged in SecurityContext securityContext = SecurityContextHolder.getContext(); securityContext.setAuthentication(auth); // Finally load the user data ShopAdminDTO shopAdminDto = this.shopAdminRepository.findByUserEmail(userEmail); return shopAdminDto; }
This is applicationContext-security.xml
<sec:http pattern="/**" auto-config="true" use-expressions="true"/> <bean id="httpSessionSecurityContextRepository" class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'> <property name='allowSessionCreation' value='false' /> </bean> <bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"> <constructor-arg ref="httpSessionSecurityContextRepository" /> </bean> <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <constructor-arg> <list> <sec:filter-chain pattern="/**" filters="securityContextPersistenceFilter" /> </list> </constructor-arg> </bean> <bean id="authenticationListener" class="com.mz.server.web.auth.CustomAuthenticationListener"/> <bean id="authenticationProvider" class="com.mz.server.web.auth.CustomAuthenticationProvider"/> <bean id="userDetailsService" class="com.mz.server.web.service.CustomUserDetailsService"/> <sec:authentication-manager alias="authenticationManager"> <sec:authentication-provider ref="authenticationProvider"/> </sec:authentication-manager> <bean id="permissionEvaluator" class="com.mz.server.web.auth.permission.CustomPermissionEvaluator"> <constructor-arg index="0"> <map key-type="java.lang.String" value-type="com.mz.server.web.auth.permission.Permission"> <entry key="isTest" value-ref="testPermission"/> </map> </constructor-arg> </bean> <bean id="testPermission" class="com.mz.server.web.auth.permission.TestPermission"> </bean> <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <property name="permissionEvaluator" ref="permissionEvaluator"/> </bean> <sec:global-method-security authentication-manager-ref="authenticationManager" pre-post-annotations="enabled"> <sec:expression-handler ref="expressionHandler"/> </sec:global-method-security>
What fails is this part of the AbstractSecurityInterceptor#beforeInvocation :
if (debug) { logger.debug("Secure object: " + object + "; Attributes: " + attributes); } if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage( "AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } Authentication authenticated = authenticateIfRequired();
If it calls credentialsNotFound because SecurityContextHolder.getContext().getAuthentication() is null .
Comparison of failed stack trace from first login after server boot:
[http-bio-8080-exec-4] DEBUG com.mz.server.servlet.LoginServletImpl - Login request by userId: user@gmx.at [http-bio-8080-exec-4] DEBUG com.mz.server.service.LoginService - Login request for user@gmx.at [http-bio-8080-exec-4] DEBUG com.mz.server.service.LoginService - Email appears validated.. authenticating.. [http-bio-8080-exec-4] INFO com.mz.server.spring.auth.AdminAuthenticationProvider - authenticate(), User email: user@gmx.at [http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at [http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found. [http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Loading password salt for user@gmx.at [http-bio-8080-exec-4] INFO com.mz.server.repository.jooq.shop.ShopAdminRepository - Checking password for user@gmx.at [http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Password valid. [http-bio-8080-exec-4] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - getPrincipal() [http-bio-8080-exec-4] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - Setting user com.mz.server.spring.auth.ShopAdminUserDetails@8ac733b2: Username: user@gmx.at; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities to 'authenticated'. [http-bio-8080-exec-4] INFO com.mz.server.service.LoginService - User successfully authenticated [userEmail=user@gmx.at] [http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at [http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found. [http-bio-8080-exec-6] DEBUG com.mz.server.servlet.shop.ShopServletImpl - Requested available shops. [http-bio-8080-exec-6] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-6] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-6] INFO com.mz.server.servlet.shop.ShopServletImpl - SPRING_SECURITY_CONTEXT: org.springframework.security.core.context.SecurityContextImpl@259bee56: Authentication: com.mz.server.spring.auth.CustomUserAuthentication@259bee56 [http-bio-8080-exec-6] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-6] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-6] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.service.ShopService.getAvailableShops(); target is of class [com.mz.server.service.ShopService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']] [http-bio-8080-exec-6] DEBUG com.mz.server.spring.auth.CustomHttpSessionListener - AuthenticationCredentialsNotFoundEvent Jun 09, 2016 8:06:42 PM org.apache.catalina.core.ApplicationContext log SEVERE: Exception while dispatching incoming RPC call com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract java.util.List com.mz.shared.web.service.shop.ShopServlet.getAvailableShops()' threw an unexpected exception: org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:416) at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:605) ....
to the stack trace:
[http-bio-8080-exec-7] DEBUG com.mz.server.servlet.LoginServletImpl - Login request by userId: user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.service.LoginService - Login request for user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.service.LoginService - Email appears validated.. authenticating.. [http-bio-8080-exec-7] INFO com.mz.server.spring.auth.AdminAuthenticationProvider - authenticate(), User email: user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found. [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Loading password salt for user@gmx.at [http-bio-8080-exec-7] INFO com.mz.server.repository.jooq.shop.ShopAdminRepository - Checking password for user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Password valid. [http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - getPrincipal() [http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - Setting user com.mz.server.spring.auth.ShopAdminUserDetails@8ac733b2: Username: user@gmx.at; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities to 'authenticated'. [http-bio-8080-exec-7] INFO com.mz.server.service.LoginService - User successfully authenticated [userEmail=user@gmx.at] [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found. [http-bio-8080-exec-7] DEBUG com.mz.server.servlet.shop.ShopServletImpl - Requested available shops. [http-bio-8080-exec-7] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-7] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-7] INFO com.mz.server.servlet.shop.ShopServletImpl - SPRING_SECURITY_CONTEXT: org.springframework.security.core.context.SecurityContextImpl@1ea22883: Authentication: com.mz.server.spring.auth.CustomUserAuthentication@1ea22883 [http-bio-8080-exec-7] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-7] INFO com.mz.server.servlet.shop.ShopServletImpl - [http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.service.ShopService.getAvailableShops(); target is of class [com.mz.server.service.ShopService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']] [http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - isAuthenticate(): true [http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Previously Authenticated: com.mz.server.spring.auth.CustomUserAuthentication@1ea22883 [http-bio-8080-exec-7] DEBUG org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@653fccd, returned: 1 [http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Authorization successful [http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - RunAsManager did not change Authentication object [http-bio-8080-exec-7] DEBUG com.mz.server.service.ShopService - Getting available shops for .. [http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - getPrincipal() [http-bio-8080-exec-7] DEBUG com.mz.server.service.ShopService - user@gmx.at [http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Fetching shops for shop_admin_id 1
We see that the difference is that the first stacktrace is created by two streams [http-bio-8080-exec-4] and [http-bio-8080-exec-6] . I often see this behavior, that the name of the thread changes, and this exception occurs. So this is a multithreading issue.
This is the whole web.xml :
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>mz | life</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/**</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <listener> <listener-class>com.mz.server.BootstrappingServerConfig</listener-class> </listener> <servlet> <servlet-name>application</servlet-name> <servlet-class>com.mz.server.servlet.app.ApplicationDataServletImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>application</servlet-name> <url-pattern>/app/application</url-pattern> </servlet-mapping> <servlet> <servlet-name>login</servlet-name> <servlet-class>com.mz.server.servlet.LoginServletImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>login</servlet-name> <url-pattern>/app/login</url-pattern> </servlet-mapping> <servlet> <servlet-name>shop</servlet-name> <servlet-class>com.mz.server.servlet.shop.ShopServletImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>shop</servlet-name> <url-pattern>/app/shop</url-pattern> </servlet-mapping> <servlet> <servlet-name>shopadmin</servlet-name> <servlet-class>com.mz.server.servlet.shop.ShopAdminServletImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>shopadmin</servlet-name> <url-pattern>/app/shopadmin</url-pattern> </servlet-mapping> <servlet> <servlet-name>xsrf</servlet-name> <servlet-class>com.google.gwt.user.server.rpc.XsrfTokenServiceServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>xsrf</servlet-name> <url-pattern>/app/xsrf</url-pattern> </servlet-mapping> <context-param> <param-name>gwt.xsrf.session_cookie_name</param-name> <param-value>JSESSIONID</param-value> </context-param> <servlet> <servlet-name>mobile-restapi</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mobile-restapi</servlet-name> <url-pattern>/app/restapi/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>web-restapi</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/context/applicationContext-restapi.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>web-restapi</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/classes/context/root-context.xml </param-value> </context-param> </web-app>