Reputation: 27899
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.
I have added the following libraries related to Spring security to the classpath.
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.
Upvotes: 56
Views: 206746
Reputation: 5944
My issue was that I needed to properly disable security and implement my own preAuthorization. Unlike the OP, I was trying to disable authentication altogether and implement my own solution.
@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().antMatchers("/**").permitAll();
}
}
Upvotes: 1
Reputation: 2365
In my case i simply forgot to initialize spring security, this can be done by extending AbstractSecurityWebApplicationInitializer when you use an empty web.xml file.
Upvotes: 0
Reputation: 1621
This error can also be got if you use Content-Type: application/json instead of Content-Type:application/x-www-form-urlencoded
Upvotes: 0
Reputation: 61
I had the same problem when and I solved it by using the following annotation :
@EnableAutoConfiguration(exclude = {
SecurityAutoConfiguration.class
})
public class Application {...}
I think the behavior is the same as what Abhishek explained
Upvotes: 0
Reputation: 927
I encountered the same error while using SpringBoot 2.1.4, along with Spring Security 5 (I believe). After one day of trying everything that Google had to offer, I discovered the cause of error in my case. I had a setup of micro-services, with the Auth server being different from the Resource Server. I had the following lines in my application.yml which prevented 'auto-configuration' despite of having included dependencies spring-boot-starter-security
, spring-security-oauth2
and spring-security-jwt
. I had included the following in the properties (during development) which caused the error.
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
Commenting it out solved it for me.
#spring:
# autoconfigure:
# exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
Hope, it helps someone.
Upvotes: 3
Reputation: 12685
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.
Upvotes: 17
Reputation: 1791
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.
Upvotes: 1
Reputation: 388436
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.
Upvotes: 38
Reputation: 61
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>
Upvotes: 3
Reputation: 7817
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:
AuthenticationSuccessEvent
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.
Upvotes: 4