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