An Authentication object was not found in the Secu

2019-01-11 12:58发布

问题:

I'm trying to invoke a protected method from a class that implements the ApplicationListener<AuthenticationSuccessEvent> interface on successful login (Spring 3.2.2 and Spring Security 3.2.0 M1). This is my previous question.

The application runs under the following environment.

  • Spring 3.2.2
  • Spring Security 3.2.0
  • JPA 2.0
  • JSF 2.1.9
  • MySQL 5.6.11
  • JDK-7u11
  • NetBeans 7.2.1

I have added the following libraries related to Spring security to the classpath.

  • spring-security-core-3.2.0.M1.jar
  • spring-security-config-3.2.0.M1.jar
  • spring-security-web-3.2.0.M1.jar

The class that implements ApplicationListener<AuthenticationSuccessEvent> is as follows.

package loginsuccesshandler;

import admin.dao.service.StateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.stereotype.Service;

@Service
public final class AuthSuccessHandler implements ApplicationListener<AuthenticationSuccessEvent>
{
    @Autowired
    private StateService stateService;

    @Override
    public void onApplicationEvent(AuthenticationSuccessEvent event) 
    {
        System.out.println(event.getAuthentication());
        System.out.println("rowCount = "+stateService.rowCount());
    }
}

This prevents a user from being logged in even with correct credentials with the following message (it is just an example. Counting the number of states upon successful authentication is not required at all).

An Authentication object was not found in the SecurityContext


The event is raised. The first statement inside the onApplicationEvent() method displays the following.

org.springframework.security.authentication.UsernamePasswordAuthenticationToken@45264a59: Principal: org.springframework.security.core.userdetails.User@586034f:Username: admin; 
Password: [PROTECTED]; 
Enabled: true; 
AccountNonExpired: true; 
credentialsNonExpired: true; 
AccountNonLocked: true; 
Granted Authorities: ROLE_ADMIN; 
Credentials: [PROTECTED]; 
Authenticated: true; 
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@380f4: RemoteIpAddress: 127.0.0.1; 
SessionId: 88777A678DC5BB0272F84CA4BC61FAF2;
Granted Authorities: ROLE_ADMIN

So it appears that the user is authenticated and the authentication object is available.


My springSecurity.xml file simply looks like the following.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <http pattern="/utility/Login.jsf*" security="none"/>
    <debug/>
    <http auto-config='true' use-expressions="true" disable-url-rewriting="true">
        <session-management session-fixation-protection="newSession">
            <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
        </session-management>

        <intercept-url pattern="/admin_side/**" access="hasRole('ROLE_ADMIN')" requires-channel="any"/>
        <intercept-url pattern="/utility/Login.jsf" access="permitAll" requires-channel="any"/>
        <http-basic />
        <anonymous />

        <form-login login-processing-url="/j_spring_security_check" login-page="/utility/Login.jsf" authentication-success-handler-ref="loginSuccessHandler" authentication-failure-handler-ref="authenticationFailureHandler"/>
        <logout logout-success-url="/utility/Login.jsf" invalidate-session="true" delete-cookies="JSESSIONID"/>
    </http>

    <authentication-manager>
       <authentication-provider>
            <jdbc-user-service data-source-ref="dataSource"
               users-by-username-query="select email_id, password, enabled from user_table where lower(email_id)=lower(?)"
               authorities-by-username-query="select ut.email_id, ur.authority from user_table ut, user_roles ur where ut.user_id=ur.user_id and lower(ut.email_id)=lower(?)"/>
       </authentication-provider>
    </authentication-manager>

    <beans:bean id="loginSuccessHandler" class="loginsuccesshandler.LoginSuccessHandler"/>
    <beans:bean id="authenticationFailureHandler" class="loginsuccesshandler.AuthenticationFailureHandler" />        

    <global-method-security secured-annotations="enabled" pre-post-annotations="enabled" proxy-target-class="false">
        <protect-pointcut expression="execution(* admin.dao.*.*(..))" access="ROLE_ADMIN"/>
    </global-method-security>
</beans:beans>

The Spring security works fine, when the following lines of XML is omitted from the spring-security.xml file.

<global-method-security secured-annotations="enabled" proxy-target-class="false">
     <protect-pointcut expression="execution(* admin.dao.*.*(..))" access="ROLE_ADMIN"/>
</global-method-security>

Can a protected method (with method security applied) be invoked from a class implementing the ApplicationListener<AuthenticationSuccessEvent> interface? If yes, then what is missing in my case? I have clicked thousands of links so far but couldn't find a single clue.


The application-context.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:component-scan base-package="admin.mangedbean loginsuccesshandler" use-default-filters="false">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
        <context:include-filter expression="org.springframework.web.bind.annotation.ControllerAdvice" type="annotation"/>  
    </context:component-scan>

    <mvc:annotation-driven/>
    <context:annotation-config/>

    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>            

    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory" >
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.enable_lazy_load_no_trans">true</prop>
            </props>
        </property>

        <property name="jpaPropertyMap">
            <map>
              <entry key="eclipselink.weaving" value="false"/>
            </map>
        </property>

        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/jdbc/social_networking"/>
    </bean>

    <!--The bean shown in the beginning is configured here-->
    <bean id="authSuccessHandler" class="loginsuccesshandler.AuthSuccessHandler"/> 

    <bean id="testService" class="admin.dao.TestDAO"/>
    <bean id="stateService" class="admin.dao.StateDAO"/>
    <bean id="sharableService" class="admin.dao.SharableDAO"/>
</beans>

The web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">        

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext.xml
            /WEB-INF/spring-security.xml
        </param-value>
    </context-param>

    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <!--<param-value>Development</param-value>-->
        <param-value>Production</param-value>
    </context-param>

    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/log4j.properties</param-value>
    </context-param>

    <context-param>
        <param-name>log4jExposeWebAppRoot</param-name>
        <param-value>false</param-value>
    </context-param>

    <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>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>        

    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>

    <security-constraint>
        <display-name>Restrict direct access to XHTML files</display-name>
        <web-resource-collection>
            <web-resource-name>XHTML files</web-resource-name>
            <url-pattern>*.xhtml</url-pattern>
        </web-resource-collection>
        <auth-constraint />
    </security-constraint> 

    <session-config>
        <session-timeout>
            120
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>/utility/Login.jsf</welcome-file>
    </welcome-file-list>

    <resource-ref>
        <res-ref-name>jdbc/social_networking</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
</web-app>

The debug information can be seen below, when an attempt is made to login which ultimately fails.

DEBUG [http-apr-8080-exec-55] (AntPathRequestMatcher.java:116) - Checking match of request : '/j_spring_security_check'; against '/utility/login.jsf*'
DEBUG [http-apr-8080-exec-55] (AntPathRequestMatcher.java:116) - Checking match of request : '/j_spring_security_check'; against '/utility/login.jsf*'
DEBUG [http-apr-8080-exec-55] (FilterChainProxy.java:337) - /j_spring_security_check at position 1 of 13 in additional filter chain; firing Filter: 'ChannelProcessingFilter'
DEBUG [http-apr-8080-exec-55] (AntPathRequestMatcher.java:116) - Checking match of request : '/j_spring_security_check'; against '/admin_side/**'
DEBUG [http-apr-8080-exec-55] (AntPathRequestMatcher.java:116) - Checking match of request : '/j_spring_security_check'; against '/utility/login.jsf'
DEBUG [http-apr-8080-exec-55] (FilterChainProxy.java:337) - /j_spring_security_check at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG [http-apr-8080-exec-55] (HttpSessionSecurityContextRepository.java:139) - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG [http-apr-8080-exec-55] (HttpSessionSecurityContextRepository.java:85) - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@1103da5. A new one will be created.
DEBUG [http-apr-8080-exec-55] (FilterChainProxy.java:337) - /j_spring_security_check at position 3 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
DEBUG [http-apr-8080-exec-55] (FilterChainProxy.java:337) - /j_spring_security_check at position 4 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG [http-apr-8080-exec-55] (FilterChainProxy.java:337) - /j_spring_security_check at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG [http-apr-8080-exec-55] (FilterChainProxy.java:337) - /j_spring_security_check at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG [http-apr-8080-exec-55] (AbstractAuthenticationProcessingFilter.java:189) - Request is to process authentication
DEBUG [http-apr-8080-exec-55] (ProviderManager.java:152) - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
DEBUG [http-apr-8080-exec-55] (JdbcTemplate.java:637) - Executing prepared SQL query
DEBUG [http-apr-8080-exec-55] (JdbcTemplate.java:572) - Executing prepared SQL statement [select email_id, password, enabled from user_table where lower(email_id)=lower(?)]
DEBUG [http-apr-8080-exec-55] (DataSourceUtils.java:110) - Fetching JDBC Connection from DataSource
DEBUG [http-apr-8080-exec-55] (DataSourceUtils.java:327) - Returning JDBC Connection to DataSource
DEBUG [http-apr-8080-exec-55] (JdbcTemplate.java:637) - Executing prepared SQL query
DEBUG [http-apr-8080-exec-55] (JdbcTemplate.java:572) - Executing prepared SQL statement [select ut.email_id, ur.authority from user_table ut, user_roles ur where ut.user_id=ur.user_id and lower(ut.email_id)=lower(?)]
DEBUG [http-apr-8080-exec-55] (DataSourceUtils.java:110) - Fetching JDBC Connection from DataSource
DEBUG [http-apr-8080-exec-55] (DataSourceUtils.java:327) - Returning JDBC Connection to DataSource
DEBUG [http-apr-8080-exec-55] (AbstractBeanFactory.java:246) - Returning cached instance of singleton bean 'authSuccessHandler'
DEBUG [http-apr-8080-exec-55] (AbstractBeanFactory.java:246) - Returning cached instance of singleton bean 'org.springframework.security.core.session.SessionRegistryImpl#0'
DEBUG [http-apr-8080-exec-55] (AbstractFallbackTransactionAttributeSource.java:106) - Adding transactional method 'rowCount' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
DEBUG [http-apr-8080-exec-55] (DelegatingMethodSecurityMetadataSource.java:65) - Caching method [CacheKey[admin.dao.StateDAO; public abstract java.lang.Long admin.dao.service.StateService.rowCount()]] with attributes [ROLE_ADMIN]
DEBUG [http-apr-8080-exec-55] (AbstractBeanFactory.java:246) - Returning cached instance of singleton bean 'transactionManager'
DEBUG [http-apr-8080-exec-55] (AbstractPlatformTransactionManager.java:366) - Creating new transaction with name [admin.dao.StateDAO.rowCount]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
DEBUG [http-apr-8080-exec-55] (JpaTransactionManager.java:369) - Opened new EntityManager [org.hibernate.ejb.EntityManagerImpl@84ff11] for JPA transaction
DEBUG [http-apr-8080-exec-55] (JpaTransactionManager.java:408) - Not exposing JPA transaction [org.hibernate.ejb.EntityManagerImpl@84ff11] as JDBC transaction because JpaDialect [org.springframework.orm.jpa.DefaultJpaDialect@d9dbb8] does not support JDBC Connection retrieval
DEBUG [http-apr-8080-exec-55] (AbstractSecurityInterceptor.java:194) - Secure object: ReflectiveMethodInvocation: public abstract java.lang.Long admin.dao.service.StateService.rowCount(); target is of class [admin.dao.StateDAO]; Attributes: [ROLE_ADMIN]
DEBUG [http-apr-8080-exec-55] (AbstractBeanFactory.java:246) - Returning cached instance of singleton bean 'authSuccessHandler'
DEBUG [http-apr-8080-exec-55] (AbstractBeanFactory.java:246) - Returning cached instance of singleton bean 'org.springframework.security.core.session.SessionRegistryImpl#0'
DEBUG [http-apr-8080-exec-55] (AbstractPlatformTransactionManager.java:844) - Initiating transaction rollback
DEBUG [http-apr-8080-exec-55] (JpaTransactionManager.java:534) - Rolling back JPA transaction on EntityManager [org.hibernate.ejb.EntityManagerImpl@84ff11]
DEBUG [http-apr-8080-exec-55] (JpaTransactionManager.java:594) - Closing JPA EntityManager [org.hibernate.ejb.EntityManagerImpl@84ff11] after transaction
DEBUG [http-apr-8080-exec-55] (EntityManagerFactoryUtils.java:338) - Closing JPA EntityManager
DEBUG [http-apr-8080-exec-55] (AbstractAuthenticationProcessingFilter.java:346) - Authentication request failed: org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
DEBUG [http-apr-8080-exec-55] (AbstractAuthenticationProcessingFilter.java:347) - Updated SecurityContextHolder to contain null Authentication
DEBUG [http-apr-8080-exec-55] (AbstractAuthenticationProcessingFilter.java:348) - Delegating to authentication failure handler loginsuccesshandler.AuthenticationFailureHandler@14883a3
DEBUG [http-apr-8080-exec-55] (DefaultRedirectStrategy.java:36) - Redirecting to '/SocialNetworking/utility/Login.jsf'
DEBUG [http-apr-8080-exec-55] (HttpSessionSecurityContextRepository.java:269) - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
DEBUG [http-apr-8080-exec-55] (SecurityContextPersistenceFilter.java:97) - SecurityContextHolder now cleared, as request processing completed
DEBUG [http-apr-8080-exec-49] (AntPathRequestMatcher.java:116) - Checking match of request : '/utility/login.jsf'; against '/utility/login.jsf*'
DEBUG [http-apr-8080-exec-49] (AntPathRequestMatcher.java:116) - Checking match of request : '/utility/login.jsf'; against '/utility/login.jsf*'
DEBUG [http-apr-8080-exec-49] (FilterChainProxy.java:180) - /utility/Login.jsf has an empty filter list

The last thing:

When I give up this bean and unregister from the application-context.xml file, the login is made successfully but the following information can be seen on the server console.

DEBUG [http-apr-8080-exec-165] (HttpSessionSecurityContextRepository.java:139) - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG [http-apr-8080-exec-165] (HttpSessionSecurityContextRepository.java:85) - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@b910c1. A new one will be created.

回答1:

The security's authorization check part gets the authenticated object from SecurityContext, which will be set when a request gets through the spring security filter. My assumption here is that soon after the login this is not being set. You probably can use a hack as given below to set the value.

try {
    SecurityContext ctx = SecurityContextHolder.createEmptyContext();
    SecurityContextHolder.setContext(ctx);
    ctx.setAuthentication(event.getAuthentication());

    //Do what ever you want to do

} finally {
    SecurityContextHolder.clearContext();
}

Update:

Also you can have a look at the InteractiveAuthenticationSuccessEvent which will be called once the SecurityContext is set.



回答2:

This could also happens if you put a @PreAuthorize or @PostAuthorize in a Bean in creation. I would recommend to move such annotations to methods of interest.



回答3:

As pointed already by @Arun P Johny the root cause of the problem is that at the moment when AuthenticationSuccessEvent is processed SecurityContextHolder is not populated by Authentication object. So any declarative authorization checks (that must get user rights from SecurityContextHolder) will not work. I give you another idea how to solve this problem. There are two ways how you can run your custom code immidiately after successful authentication:

  1. Listen to AuthenticationSuccessEvent
  2. Provide your custom AuthenticationSuccessHandler implementation.

AuthenticationSuccessHandler has one important advantage over first way: SecurityContextHolder will be already populated. So just move your stateService.rowCount() call into loginsuccesshandler.LoginSuccessHandler#onAuthenticationSuccess(...) method and the problem will go away.



回答4:

There is similar issue. I added listener as given here

https://stackoverflow.com/questions/3145936/spring-security-j-spring-security-logout-problem

It worked for me adding below lines to web.xml. Posting it very late, should help someone looking for answer.

<listener>
    <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>


回答5:

For me, the problem was a ContextRefreshedEvent handler. I was doing some data initilization but at that point in the application the Authentication had not been set. It was a catch 22 since the system needed an authentication to authorize and it needed authorization to get the authentication details :). I ended up loosening the authorization from a class level to a method level.