web-dev-qa-db-fra.com

Un objet d'authentification n'a pas été trouvé dans SecurityContext - Spring 3.2.2

J'essaie d'appeler une méthode protégée à partir d'une classe qui implémente l'interface ApplicationListener<AuthenticationSuccessEvent> en cas de connexion réussie (Spring 3.2.2 et Spring Security 3.2.0 M1). Ceci est ma question précédente.

L'application s'exécute dans l'environnement suivant.

  • Printemps 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

J'ai ajouté les bibliothèques suivantes liées à la sécurité de Spring au 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

La classe qui implémente ApplicationListener<AuthenticationSuccessEvent> est la suivante.

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());
    }
}

Cela empêche un utilisateur d'être connecté même avec les informations d'identification correctes avec le message suivant (il ne s'agit que d'un exemple. Compter le nombre d'états en cas d'authentification réussie n'est pas du tout requis).

Un objet d'authentification n'a pas été trouvé dans le SecurityContext


L'événement est levé. La première instruction de la méthode onApplicationEvent() affiche les informations suivantes.

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

Il semble donc que l'utilisateur est authentifié et que l'objet d'authentification est disponible.


Mon fichier springSecurity.xml ressemble simplement à ce qui suit.

<?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>

La sécurité Spring fonctionne correctement lorsque les lignes XML suivantes sont omises du fichier spring-security.xml.

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

Une méthode protégée (avec la sécurité de méthode appliquée) peut-elle être invoquée à partir d'une classe implémentant l'interface ApplicationListener<AuthenticationSuccessEvent>? Si oui, que manque-t-il dans mon cas? J'ai cliqué sur des milliers de liens jusqu'à présent, mais je n'ai trouvé aucun indice.


Le fichier application-context.xml.

<?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>

Le fichier web.xml.

<?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>

Les informations de débogage peuvent être consultées ci-dessous, lorsqu'une tentative de connexion qui échoue finalement est effectuée.

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

La dernière chose:

Lorsque j'abandonne ce bean et que je désinscris dans le fichier application-context.xml, la connexion est établie avec succès, mais les informations suivantes sont visibles sur la console du serveur.

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.
33
Tiny

La partie de contrôle d'autorisation de la sécurité obtient l'objet authentifié de SecurityContext, qui sera défini lorsqu'une demande passera par le filtre de sécurité à ressort. Mon hypothèse est que peu de temps après la connexion, cela n’est pas défini. Vous pouvez probablement utiliser un hack comme indiqué ci-dessous pour définir la valeur.

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

    //Do what ever you want to do

} finally {
    SecurityContextHolder.clearContext();
}

Mettre à jour:

Vous pouvez également consulter/ InteractiveAuthenticationSuccessEvent qui sera appelé une fois que la variable SecurityContext est définie.

25
Arun P Johny

Cela peut aussi arriver si vous mettez un @PreAuthorize ou @PostAuthorize dans un Bean en création. Je recommanderais de déplacer ces annotations vers les méthodes d'intérêt.

5
EliuX

Comme l'a déjà souligné @Arun P Johny, le problème est dû au fait qu'au moment où AuthenticationSuccessEvent est traité, SecurityContextHolder n'est pas rempli par l'objet Authentication. Ainsi, toutes les vérifications d'autorisation déclaratives (qui doivent obtenir les droits d'utilisateur de SecurityContextHolder) ne fonctionneront pas. Je vous donne une autre idée sur la façon de résoudre ce problème. Vous pouvez exécuter votre code personnalisé immédiatement après une authentification réussie de deux manières:

  1. Écoutez AuthenticationSuccessEvent
  2. Fournissez votre implémentation AuthenticationSuccessHandler personnalisée.

AuthenticationSuccessHandler a un avantage important sur la première façon: SecurityContextHolder sera déjà rempli. Il suffit donc de déplacer votre appel stateService.rowCount() dans la méthode loginsuccesshandler.LoginSuccessHandler#onAuthenticationSuccess(...) et le problème disparaîtra.

3
Maksym Demidas

Il y a un problème similaire. J'ai ajouté auditeur comme donné ici

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

Cela a fonctionné pour moi d’ajouter des lignes ci-dessous à web.xml . L’afficher très tardivement devrait aider les personnes à la recherche d’une réponse.

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

Pour moi, le problème était un gestionnaire ContextRefreshedEvent. Je faisais un peu d'initialisation des données mais à ce stade de l'application, l'authentification n'avait pas encore été définie. C'était un piège 22 puisque le système avait besoin d'une authentification à autoriser et qu'il avait besoin d'une autorisation pour obtenir les détails de l'authentification :). J'ai fini par relâcher l'autorisation d'un niveau de classe à un niveau de méthode.

1
Miguel Pereira