How to bypass session timeout for anonymous user with Spring Security? - spring-security

I've been working with Spring Security for two weeks now and it's working well except for anonymous users and session timeouts.
Use Case #1
An anonymous user can visit the site and view public pages (/home, /about, /signup, etc.) for as long as they want (no session timeouts).
If the user selects a protected page the login screen appears.
Use Case #2
A registered user logs in and can view protected pages.
If their session times out, the invalidSessionUrl page is displayed and the user is directed to log in.
I've read a ton of SO and blog posts but I can't seem to find the solution to Use Case #1. I'm using Spring 4.1.6.RELEASE, Spring Security 4.0.2 RELEASE and Tomcat8.
Here is my security config:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/errors/**").permitAll()
.regexMatchers("/user/signup/.*").permitAll()
.antMatchers("/recipe/listRecipes*").hasAuthority("GUEST")
.antMatchers("/recipe/addRecipe*").hasAuthority("AUTHOR")
.antMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated()
.expressionHandler(secExpressionHandler())
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?err=1")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/thankyou")
.deleteCookies( "JSESSIONID" )
.invalidateHttpSession(false)
.and()
.exceptionHandling()
.accessDeniedPage("/errors/403")
.and()
.rememberMe()
.key("recipeOrganizer")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(960) //.tokenValiditySeconds(1209600)
.rememberMeParameter("rememberMe");
http
.sessionManagement()
.sessionAuthenticationErrorUrl("/errors/402")
.invalidSessionUrl("/errors/invalidSession")
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.expiredUrl("/errors/expiredSession")
.and()
.sessionFixation().migrateSession();
Any action by an anonymous user after the session expires results in an invalid session exception. The problem is in the security filter chain (see log below) the AnonymousAuthenticationFilter (#11) creates a new session, but the SessionManagementFilter (#12) retrieves the prior expired session, compares it to the new one and throws the invalidsession exception. I want this to happen for logged in users, but not for anonymous users. However, at the time the exception is thrown the security context has been destroyed so I can't know whether the prior session was from an anonymous or logged in user.
Solutions I've considered are:
Set the global session timeout to 24 hours or more then adjust the timeout for registered users in a LoginSuccessHandler and RememberMeSuccessHandler.
Turn off security for the public pages.
Create a separate cookie to indicate the type of user (anon vs. logged in), and query that in the InvalidSessionStrategy to redirect anon users to the /home page.
Create a custom filter that is executed first to identify an anon user (possible?) and simply extend the current session.
Write some javascript or jquery to periodically check the session timeout and reset it for anon users.
Solution #1 may result in issues with expired sessions on the server (not sure if this is a big deal). #2 doesn't feel right to me, especially if a public page may include any info about a logged in user. #3 might work except that the anon user could click on a public link other than the home page but get redirected to the home page because I don't think there's a way to tell in the InvalidSessionStrategy which link they were trying to access? I'm not sure if #4 will work or not - haven't tried it yet. #5 might also work but it increases network traffic?
I'm hoping that someone can point me to a practical solution. This has to be something that many sites deal with but I'm going around in circles trying to solve. Thanks in advance for any advice or tips.
Here's a portion of a log to illustrate what happens. I've set the session timeout to 60 seconds for testing purposes.
Initial access to website
20:49:29.823 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 11 of 14 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
20:49:29.839 DEBUG: org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
20:49:29.839 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 12 of 14 in additional filter chain; firing Filter: 'SessionManagementFilter'
20:49:29.839 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 13 of 14 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
20:49:29.839 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 14 of 14 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
20:49:29.839 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/'
20:49:29.839 DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /; Attributes: [permitAll]
20:49:29.854 DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
20:49:29.871 DEBUG: org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter#ff577, returned: 1
20:49:29.871 DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Authorization successful
20:49:30.136 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - principal: anonymousUser
20:49:30.167 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - authority contains: ROLE_ANONYMOUS
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session created on: Mon Sep 21 20:49:30 CDT 2015
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session last accessed on: Mon Sep 21 20:49:30 CDT 2015
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session expires after: 60 seconds
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session ID: CA34FE74B56B8EF94181B1231A7D4FF6
Session times out after 60 seconds
20:50:46.015 INFO : net.mycompany.myapp.config.SessionDestroyedListener - destroyedEvent
20:50:46.015 INFO : net.mycompany.myapp.config.SessionDestroyedListener - object:org.springframework.security.web.session.HttpSessionDestroyedEvent[source=org.apache.catalina.session.StandardSessionFacade#17827f3e]
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session created on: Mon Sep 21 20:49:30 CDT 2015
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session last accessed on: Mon Sep 21 20:49:32 CDT 2015
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session expires after: 60 seconds
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session ID: CA34FE74B56B8EF94181B1231A7D4FF6
Click on "/home" link (unsecured)
20:50:53.927 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/home'; against '/resources/**'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 1 of 14 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 2 of 14 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
20:50:53.927 DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No HttpSession currently exists
20:50:53.927 DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: null. A new one will be created.
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 3 of 14 in additional filter chain; firing Filter: 'HeaderWriterFilter'
20:50:53.927 DEBUG: org.springframework.security.web.header.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#4c668dec
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 4 of 14 in additional filter chain; firing Filter: 'CsrfFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 5 of 14 in additional filter chain; firing Filter: 'LogoutFilter'
20:50:53.927 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Request 'GET /home' doesn't match 'POST /logout
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 6 of 14 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Request 'GET /home' doesn't match 'POST /login
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 7 of 14 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 8 of 14 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 9 of 14 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 10 of 14 in additional filter chain; firing Filter: 'RememberMeAuthenticationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 11 of 14 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 12 of 14 in additional filter chain; firing Filter: 'SessionManagementFilter'
20:50:53.927 DEBUG: org.springframework.security.web.session.SessionManagementFilter - Requested session ID CA34FE74B56B8EF94181B1231A7D4FF6 is invalid.
20:50:53.927 DEBUG: org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy - Starting new session (if required) and redirecting to '/errors/invalidSession'
20:50:53.927 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - principal: anonymousUser
20:50:53.927 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - authority contains: ROLE_ANONYMOUS
20:50:53.927 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session created on: Mon Sep 21 20:50:53 CDT 2015
20:50:53.927 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session last accessed on: Mon Sep 21 20:50:53 CDT 2015
20:50:53.942 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session expires after: 60 seconds
20:50:53.942 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session ID: 6F7FD6230BC075B7768648BBBC08E3F4
20:50:53.942 DEBUG: org.springframework.security.web.session.HttpSessionEventPublisher - Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade#412cbe09]
20:50:53.942 DEBUG: org.springframework.security.web.DefaultRedirectStrategy - Redirecting to '/myapp/errors/invalidSession'

I posted a similar question a few days after this one: Why does anonymous user get redirected to expiredsessionurl by Spring Security, for which I've posted an answer. Solution #2 above works, but I don't like turning off all security even for public pages. What I ended up implementing was a combination of #3 and #4. See the linked question for the full answer.

This is a old question but I have a similar problem even now (Spring Security 5.5.1)
I found some answers but they seemed too fragile for me:
Why does anonymous user get redirected to expiredsessionurl by Spring Security (uses Cookie to detect)
Spring Security getting the acess attributes of patterns in intercept-url (using custom url interceptor)
This is what works for me (using a custom filter for Spring Security)
/**
* https://doanduyhai.wordpress.com/2012/04/21/spring-security-part-vi-session-timeout-handling-for-ajax-calls/
* 1. detect session expiry and ajax request --> send custom error code so that
* client side can detect and handle as necessary if not, it will be a redirect
* to login page and with 200 response
*
* https://forum.primefaces.org/viewtopic.php?t=16735 (use xml partial response for redirect)
* #author RadianceOng
*/
public class AjaxTimeoutRedirectFilter extends GenericFilterBean {
static Logger lg = Logger.getLogger(AjaxTimeoutRedirectFilter.class);
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
String invalidSessionUrl;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
Exception ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (ase instanceof AuthenticationException) {
throw (AuthenticationException) ase;
} else if (ase instanceof AccessDeniedException) {
AccessDeniedException ade = (AccessDeniedException) ase;
if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
logger.trace("User session expired or not logged in yet and trying to access unauthorized resource");
HttpServletRequest httpReq = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (isAjaxRequest(httpReq)) {
logger.trace("Ajax call detected, redirecting");
/**
* Session expiry check is copied from SessionManagementFilter
* accessdenied check is derived from ExceptionTranslationFilter
* No idea what is the order... This is a workaround.
*/
final String redirectUrl = invalidSessionUrl;
sendAjaxRedirect(resp, httpReq, redirectUrl);
} else {
logger.trace("Normal call detected, redirecting");
new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl).onInvalidSessionDetected(httpReq, resp);
}
} else {
throw ade;
}
}
} else {
throw ex;
}
}
}
public String getInvalidSessionUrl() {
return invalidSessionUrl;
}
public void setInvalidSessionUrl(String invalidSessionUrl) {
this.invalidSessionUrl = invalidSessionUrl;
}
private boolean isAjaxRequest(HttpServletRequest httpReq) {
return new AjaxRedirectUtils().isAjaxRequest(httpReq);
}
private void sendAjaxRedirect(HttpServletResponse resp, HttpServletRequest httpReq, final String redirectUrl) throws IOException {
new AjaxRedirectUtils().sendAjaxRedirect(resp, httpReq, redirectUrl);
}
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
/**
* #see
* org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
protected void initExtractorMap() {
super.initExtractorMap();
registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
public Throwable extractCause(Throwable throwable) {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
return ((ServletException) throwable).getRootCause();
}
});
}
}
}
In the XML configuration for Spring Security, add the filter after EXCEPTION_TRANSLATION_FILTER so that it can detect access denied
<http>
<custom-filter ref="ajaxTimeoutRedirectFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
</http>
Note that if you use the built-in session management, make sure you do not use the invalid-session-url or related else that will take effect instead
<http>
<session-management>
</session-management>
</http>
This filter also caters for Ajax redirection. I left that out as that is not relevant to the question
In summary, what this achieves is to redirect only if access denied. So non- logged-in users will not get redirected unless they access a unauthorized page. Logged-in users whose sessions expire will get redirected when they access an unauthorized page.

Related

Authentication failing spring security wildfly

I am trying to migrate my spring application from tomcat to wildfly. I can't login anymore. It seems that the initial successful authentication gets lost or over written and then an anonymous session gets created.
spring security: 4.0.1.RELEASE
wildfly 8.2.0
I'm stuck, can't figure it out. Any help or suggestions would be appreciated.
Thanks
Here are the excerpts from the logs:
Successful authentication:
17:13:43,182 FINE [org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter] (default task-6) Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#fc115a3: Principal: myapp.springSupport.AccountUser#bb94ce99: Username: test1976; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_sysadmin,user; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_sysadmin, user
17:13:43,183 FINE [org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler] (default task-6) Using default Url: /
17:13:43,183 FINE [org.springframework.security.web.DefaultRedirectStrategy] (default task-6) Redirecting to '/myapp/'
17:13:43,183 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-6) HttpSession being created as SecurityContext is non-default
17:13:43,184 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-6) SecurityContext 'org.springframework.security.core.context.SecurityContextImpl#fc115a3: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#fc115a3: Principal: myapp.springSupport.AccountUser#bb94ce99: Username: test1976; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_sysadmin,user; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_sysadmin, user' stored to HttpSession: 'io.undertow.servlet.spec.HttpSessionImpl#725caa25
17:13:43,186 FINE [org.springframework.security.web.context.SecurityContextPersistenceFilter] (default task-6) SecurityContextHolder now cleared, as request processing completed
17:13:43,202 FINE [org.springframework.security.web.FilterChainProxy] (default task-9) /index.jsp at position 1 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
17:13:43,202 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-9) No HttpSession currently exists
17:13:43,202 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-9) No SecurityContext was available from the HttpSession: null. A new one will be created.
Here is where the anonymous session gets created:
17:13:43,205 FINE [org.springframework.security.web.authentication.AnonymousAuthenticationFilter] (default task-9) Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
17:13:43,205 FINE [org.springframework.security.web.FilterChainProxy] (default task-9) /index.jsp at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
17:13:43,205 FINE [org.springframework.security.web.session.SessionManagementFilter] (default task-9) Requested session ID 4B9EACEB7190B99CC7A1E248D8E4495B is invalid.
17:13:43,205 FINE [org.springframework.security.web.FilterChainProxy] (default task-9) /index.jsp at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
17:13:43,206 FINE [org.springframework.security.web.FilterChainProxy] (default task-9) /index.jsp at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
17:13:43,206 FINE [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (default task-9) Checking match of request : '/index.jsp'; against '/srv/private/**'
17:13:43,206 FINE [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (default task-9) Public object - authentication not attempted
17:13:43,206 FINE [org.springframework.security.web.FilterChainProxy] (default task-9) /index.jsp reached end of additional filter chain; proceeding with original chain
17:13:43,207 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-9) SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
17:13:43,207 FINE [org.springframework.security.web.access.ExceptionTranslationFilter] (default task-9) Chain processed normally
17:13:43,207 FINE [org.springframework.security.web.context.SecurityContextPersistenceFilter] (default task-9) SecurityContextHolder now cleared, as request processing completed
17:13:43,211 FINE [org.springframework.security.web.FilterChainProxy] (default task-12) /srv/home at position 1 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
17:13:43,211 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-12) No HttpSession currently exists
17:13:43,211 FINE [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-12) No SecurityContext was available from the HttpSession: null. A new one will be created.
My spring security config:
<security:http auto-config="true">
<security:intercept-url pattern="/srv/private/**"
access="hasAuthority('user')"/>
<!-- enable csrf protection -->
<security:csrf disabled="true"/>
</security:http>
Sooooo, I feel like a bit on a knob.
It turns out that when I use chrome and not firefox, I don't get this problem.
Firefox was mangling the JSESSIONIDs.
This is because I've been too busy working to upgrade my ubuntu and I am using a very out of date firefox browser.
Did I mention I feel like a knob?

Using Spring Security and Requestmap fails in Grails

I am new to Grails. I started a new project from scratch and added Spring Security Core as authentication and authorisation (I am using GGTS as a tool). My problem is that if I start using Requestmap, it does not work at all, even if I am using the instructions I have found all around the net. Here is my configurations.
Buildconfig.groovy:
compile ':spring-security-core:2.0-RC4'
Command I used to create default objects
s2-quickstart com.company.foobar User Privilege Requestmap
Config.groovy
grails.plugin.springsecurity.rejectIfNoRule = true
grails.plugin.springsecurity.fii.rejectPublicInvocations = false
grails.plugin.springsecurity.logout.postOnly = false
// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.company.foobar.User'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.company.foobar.UserPrivilege'
grails.plugin.springsecurity.authority.className = 'com.company.foobar.Privilege'
grails.plugin.springsecurity.requestMap.className = 'com.company.foobar.Requestmap'
grails.plugin.springsecurity.securityConfigType = grails.plugin.springsecurity.SecurityConfigType.Requestmap
//**** I have tried both above and below (below is default one).
grails.plugin.springsecurity.securityConfigType = 'Requestmap'
BootStrap.groovy
for (String url in [
'/', '/index', '/index.gsp', '/**/favicon.ico',
'/assets/**', '/**/js/**', '/**/css/**', '/**/images/**',
'/login', '/login.*', '/login/**',
'/logout', '/logout.*', '/logout/**']) {
new Requestmap(url: url, configAttribute: 'ROLE_ANONYMOUS').save()
}
// I have tried both these (above and below)
// I have tried configuration attribute as
// IS_AUTHENTICATED_ANONYMOUSLY, permitAll
// and ROLE_ANONYMOUS (and few others too)
new Requestmap(url: '/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save();
new Requestmap(url: '/logout/**', configAttribute: 'ROLE_ANONYMOUS').save();
new Requestmap(url: '/login/**', configAttribute: 'ROLE_ANONYMOUS').save()
new Requestmap(url: '/index/**', configAttribute: 'ROLE_ANONYMOUS').save();
Note: DB gets populated correctly.
Thing is that DB gets populated correctly, but I get these errors:
hierarchicalroles.RoleHierarchyImpl setHierarchy() - The following role hierarchy was set:
intercept.FilterSecurityInterceptor Validated configuration attributes
web.DefaultSecurityFilterChain Creating filter chain: Ant [pattern='/**'], [org.springframework.security.web.context.SecurityContextPersistenceFilter#7f4446e0, grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter#5b895d66, grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter#1753027d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#4ac86881, grails.plugin.springsecurity.web.filter.GrailsRememberMeAuthenticationFilter#2b451382, grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter#4403d1ff, org.springframework.security.web.access.ExceptionTranslationFilter#56cfdf3b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#6948c703]
|Server running. Browse to http://localhost:8080/foobar
....matcher.AntPathRequestMatcher Request '/index.gsp' matched by universal pattern '/**'
web.FilterChainProxy /index.gsp at position 1 of 8 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
context.HttpSessionSecurityContextRepository No HttpSession currently exists
context.HttpSessionSecurityContextRepository No SecurityContext was available from the HttpSession: null. A new one will be created.
web.FilterChainProxy /index.gsp at position 2 of 8 in additional filter chain; firing Filter: 'MutableLogoutFilter'
web.FilterChainProxy /index.gsp at position 3 of 8 in additional filter chain; firing Filter: 'RequestHolderAuthenticationFilter'
web.FilterChainProxy /index.gsp at position 4 of 8 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
web.FilterChainProxy /index.gsp at position 5 of 8 in additional filter chain; firing Filter: 'GrailsRememberMeAuthenticationFilter'
web.FilterChainProxy /index.gsp at position 6 of 8 in additional filter chain; firing Filter: 'GrailsAnonymousAuthenticationFilter'
web.FilterChainProxy /index.gsp at position 7 of 8 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
web.FilterChainProxy /index.gsp at position 8 of 8 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
intercept.FilterSecurityInterceptor Secure object: FilterInvocation: URL: /index.gsp; Attributes: [_DENY_]
intercept.FilterSecurityInterceptor Previously Authenticated: grails.plugin.springsecurity.authentication.GrailsAnonymousAuthenticationToken#dc4337e: Principal: org.springframework.security.core.userdetails.User#dc730200: Username: __grails.anonymous.user__; Password: [PROTECTED]; Enabled: false; AccountNonExpired: false; credentialsNonExpired: false; AccountNonLocked: false; Granted Authorities: ROLE_ANONYMOUS; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
hierarchicalroles.RoleHierarchyImpl getReachableGrantedAuthorities() - From the roles [ROLE_ANONYMOUS] one can reach [ROLE_ANONYMOUS] in zero or more steps.
access.ExceptionTranslationFilter Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at grails.plugin.springsecurity.access.vote.AuthenticatedVetoableDecisionManager.decide(AuthenticatedVetoableDecisionManager.java:47)
at grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.java:53)
at grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter.doFilter(RequestHolderAuthenticationFilter.java:49)
at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:82)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
savedrequest.HttpSessionRequestCache DefaultSavedRequest added to Session: DefaultSavedRequest[http://localhost:8080/foobar/]
access.ExceptionTranslationFilter Calling Authentication entry point.
web.DefaultRedirectStrategy Redirecting to 'http://localhost:8080/foobar/login/auth'
After this I get looping error from browser (it tries and tries to login/auth page getting same answer all the time). I have checked answers in the stackoverflow, but my configs are like in those answers, and still aint helping.
I have cheked this, its not helping me, Grails spring security fails to present the login page due to a redirect loop (I have configuration like in answer above).
What works
If I take out of request map and use static definitions in Config.groovy everything works like a charm, but I need to use the DB for configuration (to go further from there).
Seems to be an [issue][1] related to the *[hibernate4 plugin][2]*.
Using Grails 2.5 the hibernate-plugin installed by default (BuildConfig.groovy) is:
runtime ":hibernate4:4.3.8.1" // or ":hibernate:3.6.10.18"
This is obviously not working for securityConfigType = 'Requestmap'
So I tried ...
4.3.8.2-SNAPSHOT: same problem.
4.3.6.1: same problem.
4.3.5.4: seems to work well
May be it is an option for you to downgrade your hibernate4 plugin:
runtime ":hibernate4:4.3.5.4"

Configuring spring security using oAuth and form based authentication

I have a server application with two components:
a) A set of REST API that are secured using oAuth ( Spring security oAuth)
b) A dashboard for management with role based UI
For business reasons, these two components need to be co-hosted i.e deployed as a single war.
Till now we only had oAuth for our REST API's and all was well.
The problem started when we tried to use form based authentication for the dashboard. Now, when we access REST API's without an oAuth token, it just redirects to the login page instead of giving a 401 unauthorized error.
Our configuration is like this:
Form based authentication(WebSecurityConfigurerAdapter):
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**","/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
oAuth:
Resource provider configuration:
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')")
We would basically like to configure different API to be secured differently. REST API consumed by client applications have to be secured by oAuth and Spring MVC API rendering dashboard pages with form based authentication.
Is this even possible?
EDIT:
Add ordering and able to get 401 unauthorized message on accessing oauth protected REST APIs. Form login doesnt work though. I am able to access all dashboard pages without login.
More code snippets:
#Configuration
#EnableResourceServer
public class ResourceProviderConfiguration extends ResourceServerConfigurerAdapter {
.....
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')").
.and()
.requestMatchers()
.antMatchers("/abc","/xyz","/others");
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationProviderConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
ClientDetailsService webClientDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
.......
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
......
}
#Configuration
#EnableWebSecurity
#Order(5)
public class UserAuthenticationConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**",
"/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
}
}
Spring security logs:
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/token']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/token'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/token_key']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/token_key'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/check_token']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/check_token'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - No matches found
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/abc/**']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/abc/**'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/xyz/**']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/xyz/**'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - No matches found
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade#7f8059. A new one will be created.
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#cebda04
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/logout'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 5 of 11 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET '/def'' doesn't match 'POST /login/new
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#0: RemoteIpAddress: 127.0.0.1; SessionId: 794828541EF505314237BBC81C2ACAF4; Granted Authorities: ROLE_ANONYMOUS'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/css/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/img/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/login/**'
DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Public object - authentication not attempted
DEBUG o.s.security.web.FilterChainProxy - '/def' reached end of additional filter chain; proceeding with original chain
DEBUG o.s.web.servlet.DispatcherServlet - DispatcherServlet with name 'webservice' processing GET request for ['/handler/def']
Final Configuration that worked:
1. Adding #Order to the web security configurer, after the resource provider configuration which defaults to 3.
Ensuring that WebSecurityConfigurerAdapter has .anyRequest().authenticated() config
#Configuration
#EnableResourceServer
public class ResourceProviderConfiguration extends ResourceServerConfigurerAdapter {
.....
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')").
.and()
.requestMatchers()
.antMatchers("/abc","/xyz","/others");
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationProviderConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
ClientDetailsService webClientDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
.......
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
......
}
#Configuration
#EnableWebSecurity
#Order(5)
public class UserAuthenticationConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**",
"/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
}
}
I can see it checking your permitAll() matchers in the logs, but there's no sign of the anyRequest().authenticated(). You need to add a request matcher to the HttpSecurity as well (i.e. http.requestMatchers().anyRequest()).

Spring Security Logout Redirects to Logout Success and then Immediately to Invalid Session Page

As per the post Spring Security: Redirect to invalid-session-url instead of logout-success-url on successful logout, when logging out of a session Spring Security redirects to the user defined invalid-session-url.
<session-management invalid-session-url="/invalidSession.jsp">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
However, if a logout-success url is set
<logout invalidate-session="true"
logout-success-url="/logoutSuccess.jsp"
logout-url="/logout" />
Spring still redirects to the invalid session URL after redirecting to the logout-success url. This happens even when the logoutSuccess url is unsecured. I.e.,
<intercept-url pattern="/logoutSuccess.jsp*" access="permitAll"/>
Is this a Spring bug? Since the logout-success-url is set and unsecured it seems that the user should not be redirected to invalid session url after reaching the logout success url.
The log looks as follows:
INFO: [DEBUG,SimpleUrlLogoutSuccessHandler] Using default Url: /logoutSuccess.jsp
INFO: [DEBUG,DefaultRedirectStrategy] Redirecting to '/Application/logoutSuccess.jsp'
INFO: [DEBUG,HttpSessionSecurityContextRepository] SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
INFO: [DEBUG,SecurityContextPersistenceFilter] SecurityContextHolder now cleared, as request processing completed
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 1 of 10 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 2 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
INFO: [DEBUG,HttpSessionSecurityContextRepository] No HttpSession currently exists
INFO: [DEBUG,HttpSessionSecurityContextRepository] No SecurityContext was available from the HttpSession: null. A new one will be created.
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 3 of 10 in additional filter chain; firing Filter: 'LogoutFilter'
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 4 of 10 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 5 of 10 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 6 of 10 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 7 of 10 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
INFO: [DEBUG,AnonymousAuthenticationFilter] Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
INFO: [DEBUG,FilterChainProxy] /logoutSuccess.jsp at position 8 of 10 in additional filter chain; firing Filter: 'SessionManagementFilter'
INFO: [DEBUG,SessionManagementFilter] Requested session ID a396530a530b344ff531ab657e32 is invalid.
INFO: [DEBUG,SimpleRedirectInvalidSessionStrategy] Starting new session (if required) and redirecting to '/invalidsession.jsp'
INFO: [DEBUG,HttpSessionEventPublisher] Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade#564c4200]
INFO: [DEBUG,DefaultRedirectStrategy] Redirecting to '/Application/invalidsession.jsp'
This is explained in the reference manual.
To summarise, the "invalid session" functionality is based on the validity of the submitted session cookie, so if you access the site (or more specifically, the security filter chain) after logging out, and you still have a JSESSIONID cookie, you may trigger this undesired behaviour.
As described in the same part of the manual, you can try using
<logout invalidate-session="true"
logout-success-url="/logoutSuccess.jsp"
logout-url="/logout" delete-cookies="JSESSIONID" />
to remove the cookie when logging out.
You must take care, sometimes using invalidate-session='true' and delete-cookies=JSESSIONID together along with limited number of sessions that a user can have, might get you "Maximum sessions of 1 for this principal exceeded" error when you try to log in even after you log out.
It is advisable to use only Delete-cookies to remove necessary session information when u are using Spring Security 3.1 and above.
Configure the logout as below to delete cookies in the security configure WebSecurityConfigurerAdapter class.
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//set access to all pages including session time out page where session time out is set in the application.properties page
httpSecurity
.authorizeRequests().antMatchers("/","/products","/product/show/*","/session","/console/*","/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll();
//delete cookies so it won't get forwarded to session out page
httpSecurity.logout().deleteCookies("auth_code", "JSESSIONID").invalidateHttpSession(true);
httpSecurity.csrf().disable();
httpSecurity.headers().frameOptions().disable();
httpSecurity.sessionManagement().invalidSessionUrl("/session");
}
And finally in the session expiration forward page delete the cookies manually
#RequestMapping("/session")
String session(HttpServletRequest request,HttpServletResponse response){
SecurityContextHolder.clearContext();
HttpSession session= request.getSession(false);
Cookie[] cookies = request.getCookies();
// Delete all the cookies
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
cookies[i].setValue(null);
cookies[i].setMaxAge(0);
response.addCookie(cookie);
}
}
SecurityContextHolder.clearContext();
if(session != null) {
session.invalidate();
}
return "session";
}

Spring Security Auto Login not Persisted in HttpSession

The below code creates a Spring Authentication object that has the role_user associated as when I looked at the SecurityContext context = SecurityContextHolder.getContext(); at the very end it does have the ROLE_USER and a principal of UserDetails so somehow its not getting associated to my HttpSession. Any ideas? My exception is below as well
public String login(){
if(signUpDetailBean.getEmail() != null){
sers currentUser = userManager.getUser(signUpDetailBean.getEmail());
authenticateUserAndSetSession(currentUser, (HttpServletRequest) FacesUtils.getExternalContext().getRequest());
clearForm();
return "/registered/home.html";
}else{
clearForm();
return "/auth/login.html";
}
}
private void authenticateUserAndSetSession(Users user, HttpServletRequest request)
{
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
user.getUsername(), user.getPassword());
// generate session if one doesn't exist
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
SecurityContext context = SecurityContextHolder.getContext();
}
19:11:07,599 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/javax.faces.resource/**'
19:11:07,600 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/services/rest-api/1.0/**'
19:11:07,600 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/preregistered/*'
19:11:07,600 DEBUG FilterChainProxy:263 - /registered/home.html at position 1 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.context.SecurityContextPersistenceFilter#3486a602'
19:11:07,600 DEBUG HttpSessionSecurityContextRepository:138 - HttpSession returned null object for SPRING_SECURITY_CONTEXT
19:11:07,600 DEBUG HttpSessionSecurityContextRepository:84 - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade#1985b723. A new one will be created.
19:11:07,601 DEBUG FilterChainProxy:263 - /registered/home.html at position 2 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.logout.LogoutFilter#5b4c1313'
19:11:07,601 DEBUG FilterChainProxy:263 - /registered/home.html at position 3 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#5f787338'
19:11:07,601 DEBUG FilterChainProxy:263 - /registered/home.html at position 4 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.savedrequest.RequestCacheAwareFilter#5cd4927f'
19:11:07,601 DEBUG DefaultSavedRequest:316 - pathInfo: both null (property equals)
19:11:07,602 DEBUG DefaultSavedRequest:316 - queryString: both null (property equals)
19:11:07,602 DEBUG DefaultSavedRequest:338 - requestURI: arg1=/dreamcatcher/registered/modify.html; arg2=/dreamcatcher/registered/home.html (property not equals)
19:11:07,602 DEBUG HttpSessionRequestCache:75 - saved request doesn't match
19:11:07,603 DEBUG FilterChainProxy:263 - /registered/home.html at position 5 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#7ddff76'
19:11:07,603 DEBUG FilterChainProxy:263 - /registered/home.html at position 6 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter#8afbefd'
19:11:07,603 DEBUG FilterChainProxy:263 - /registered/home.html at position 7 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.AnonymousAuthenticationFilter#775a9fdf'
19:11:07,604 DEBUG AnonymousAuthenticationFilter:68 - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#d45457b8: Principal: guest; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#1de60: RemoteIpAddress: 0:0:0:0:0:0:0:1%0; SessionId: 2A7602A6013D6200B7A663CEED58C478; Granted Authorities: ROLE_ANONYMOUS'
19:11:07,604 DEBUG FilterChainProxy:263 - /registered/home.html at position 8 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.session.SessionManagementFilter#51d394ab'
19:11:07,605 DEBUG FilterChainProxy:263 - /registered/home.html at position 9 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.access.ExceptionTranslationFilter#19c59085'
19:11:07,605 DEBUG FilterChainProxy:263 - /registered/home.html at position 10 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#3c92218c'
19:11:07,605 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/**/*.xhtml'
19:11:07,606 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/auth/**'
19:11:07,606 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/auth/*'
19:11:07,607 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/registered/home.html'; against '/registered/*'
19:11:07,607 DEBUG FilterSecurityInterceptor:191 - Secure object: FilterInvocation: URL: /registered/home.html; Attributes: [ROLE_USER]
19:11:07,608 DEBUG FilterSecurityInterceptor:291 - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#d45457b8: Principal: guest; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#1de60: RemoteIpAddress: 0:0:0:0:0:0:0:1%0; SessionId: 2A7602A6013D6200B7A663CEED58C478; Granted Authorities: ROLE_ANONYMOUS
19:11:07,608 DEBUG AffirmativeBased:53 - Voter: org.springframework.security.access.vote.RoleVoter#44548719, returned: -1
19:11:07,616 DEBUG AffirmativeBased:53 - Voter: org.springframework.security.access.vote.AuthenticatedVoter#554ff490, returned: 0
19:11:07,636 DEBUG ExceptionTranslationFilter:151 - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:71)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:203)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:114)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:95)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:100)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:79)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:112)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:35)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:187)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:268)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:121)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:244)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:550)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:380)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:288)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
19:11:07,637 DEBUG HttpSessionRequestCache:41 - DefaultSavedRequest added to Session: DefaultSavedRequest[http://localhost:8080/dreamcatcher/registered/home.html]
19:11:07,638 DEBUG ExceptionTranslationFilter:175 - Calling Authentication entry point.
19:11:07,638 DEBUG DefaultRedirectStrategy:36 - Redirecting to 'http://localhost:8080/dreamcatcher/auth/login.html'
19:11:07,639 DEBUG SecurityContextPersistenceFilter:90 - SecurityContextHolder now cleared, as request processing completed
19:11:07,651 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/auth/login.html'; against '/javax.faces.resource/**'
19:11:07,652 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/auth/login.html'; against '/services/rest-api/1.0/**'
19:11:07,652 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/auth/login.html'; against '/preregistered/*'
19:11:07,652 DEBUG FilterChainProxy:263 - /auth/login.html at position 1 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.context.SecurityContextPersistenceFilter#3486a602'
19:11:07,653 DEBUG HttpSessionSecurityContextRepository:138 - HttpSession returned null object for SPRING_SECURITY_CONTEXT
19:11:07,653 DEBUG HttpSessionSecurityContextRepository:84 - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade#1985b723. A new one will be created.
19:11:07,653 DEBUG FilterChainProxy:263 - /auth/login.html at position 2 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.logout.LogoutFilter#5b4c1313'
19:11:07,653 DEBUG FilterChainProxy:263 - /auth/login.html at position 3 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#5f787338'
19:11:07,654 DEBUG FilterChainProxy:263 - /auth/login.html at position 4 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.savedrequest.RequestCacheAwareFilter#5cd4927f'
19:11:07,654 DEBUG DefaultSavedRequest:316 - pathInfo: both null (property equals)
19:11:07,654 DEBUG DefaultSavedRequest:316 - queryString: both null (property equals)
19:11:07,655 DEBUG DefaultSavedRequest:338 - requestURI: arg1=/dreamcatcher/registered/home.html; arg2=/dreamcatcher/auth/login.html (property not equals)
19:11:07,655 DEBUG HttpSessionRequestCache:75 - saved request doesn't match
19:11:07,655 DEBUG FilterChainProxy:263 - /auth/login.html at position 5 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#7ddff76'
19:11:07,655 DEBUG FilterChainProxy:263 - /auth/login.html at position 6 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter#8afbefd'
19:11:07,656 DEBUG FilterChainProxy:263 - /auth/login.html at position 7 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.authentication.AnonymousAuthenticationFilter#775a9fdf'
19:11:07,656 DEBUG AnonymousAuthenticationFilter:68 - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#d45457b8: Principal: guest; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#1de60: RemoteIpAddress: 0:0:0:0:0:0:0:1%0; SessionId: 2A7602A6013D6200B7A663CEED58C478; Granted Authorities: ROLE_ANONYMOUS'
19:11:07,656 DEBUG FilterChainProxy:263 - /auth/login.html at position 8 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.session.SessionManagementFilter#51d394ab'
19:11:07,657 DEBUG FilterChainProxy:263 - /auth/login.html at position 9 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.access.ExceptionTranslationFilter#19c59085'
19:11:07,657 DEBUG FilterChainProxy:263 - /auth/login.html at position 10 of 10 in additional filter chain; firing Filter: 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#3c92218c'
19:11:07,658 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/auth/login.html'; against '/**/*.xhtml'
19:11:07,658 DEBUG AntPathRequestMatcher:72 - Checking match of request : '/auth/login.html'; against '/auth/**'
19:11:07,658 DEBUG FilterSecurityInterceptor:191 - Secure object: FilterInvocation: URL: /auth/login.html; Attributes: [ROLE_ANONYMOUS, ROLE_USER]
19:11:07,659 DEBUG FilterSecurityInterceptor:291 - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#d45457b8: Principal: guest; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#1de60: RemoteIpAddress: 0:0:0:0:0:0:0:1%0; SessionId: 2A7602A6013D6200B7A663CEED58C478; Granted Authorities: ROLE_ANONYMOUS
19:11:07,659 DEBUG AffirmativeBased:53 - Voter: org.springframework.security.access.vote.RoleVoter#44548719, returned: 1
19:11:07,659 DEBUG FilterSecurityInterceptor:212 - Authorization successful
19:11:07,660 DEBUG FilterSecurityInterceptor:222 - RunAsManager did not change Authentication object
19:11:07,660 DEBUG FilterChainProxy:252 - /auth/login.html reached end of additional filter chain; proceeding with original chain
19:11:07,672 DEBUG DefaultListableBeanFactory:430 - Creating instance of bean 'authentication'
19:11:07,677 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for com.dc.web.beans.LayoutBean com.dc.web.actions.BaseAction.layoutBean
19:11:07,678 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for com.dc.web.beans.AuthenticationBean com.dc.web.actions.Authentication.authenticationBean
19:11:07,679 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for com.dc.api.service.UserManager com.dc.web.actions.Authentication.userManager
19:11:07,679 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'userManager'
19:11:07,680 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for com.dc.api.service.Utilities com.dc.web.actions.Authentication.utilities
19:11:07,681 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'utilities'
19:11:07,681 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for com.dc.web.util.PasswordMailContentHelper com.dc.web.actions.Authentication.passwordMailContentHelper
19:11:07,682 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'passwordMailContentHelper'
19:11:07,683 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for com.dc.web.util.UsernameMailContentHelper com.dc.web.actions.Authentication.usernameMailContentHelper
19:11:07,683 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'usernameMailContentHelper'
19:11:07,684 DEBUG InjectionMetadata:82 - Processing injected method of bean 'authentication': AutowiredFieldElement for org.springframework.security.authentication.encoding.PasswordEncoder com.dc.web.actions.Authentication.passwordEncoder
19:11:07,684 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'passwordEncoder'
19:11:07,685 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'authentication'
19:11:07,690 DEBUG DefaultListableBeanFactory:430 - Creating instance of bean 'signUpDetail'
19:11:07,692 DEBUG InjectionMetadata:82 - Processing injected method of bean 'signUpDetail': AutowiredFieldElement for com.dc.web.beans.LayoutBean com.dc.web.actions.BaseAction.layoutBean
19:11:07,692 DEBUG InjectionMetadata:82 - Processing injected method of bean 'signUpDetail': AutowiredFieldElement for private com.dc.web.beans.SignUpDetailBean com.dc.web.actions.SignUpDetail.signUpDetailBean
19:11:07,693 DEBUG InjectionMetadata:82 - Processing injected method of bean 'signUpDetail': AutowiredFieldElement for private com.dc.api.service.UserManager com.dc.web.actions.SignUpDetail.userManager
19:11:07,694 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'userManager'
19:11:07,695 DEBUG InjectionMetadata:82 - Processing injected method of bean 'signUpDetail': AutowiredFieldElement for protected org.springframework.security.authentication.AuthenticationManager com.dc.web.actions.SignUpDetail.authenticationManager
19:11:07,695 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'org.springframework.security.authenticationManager'
19:11:07,696 DEBUG InjectionMetadata:82 - Processing injected method of bean 'signUpDetail': AutowiredFieldElement for com.dc.api.service.Utilities com.dc.web.actions.SignUpDetail.utilities
19:11:07,697 DEBUG DefaultListableBeanFactory:242 - Returning cached instance of singleton bean 'utilities'
19:11:07,697 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'signUpDetail'
19:11:07,716 DEBUG DefaultListableBeanFactory:430 - Creating instance of bean 'layout'
19:11:07,717 DEBUG InjectionMetadata:82 - Processing injected method of bean 'layout': AutowiredFieldElement for com.dc.web.beans.LayoutBean com.dc.web.actions.BaseAction.layoutBean
19:11:07,718 DEBUG InjectionMetadata:82 - Processing injected method of bean 'layout': AutowiredFieldElement for private com.dc.web.beans.LayoutBean com.dc.web.actions.Layout.layoutBean
19:11:07,719 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'layout'
19:11:07,738 DEBUG ExceptionTranslationFilter:98 - Chain processed normally
19:11:07,738 DEBUG HttpSessionSecurityContextRepository:271 - SecurityContext contents are anonymous - context will not be stored in HttpSession.
19:11:07,739 DEBUG SecurityContextPersistenceFilter:90 - SecurityContextHolder now cleared, as request processing completed
We need to put the security context in session. Otherwise it won't work. You can use this
SecurityContextHolder.getContext().setAuthentication(authentication);
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
I'm losing my login credentials between a successful login and the redirect from my AuthenticationSuccessHandler in one app, but not in another that's essentially doing the same things.
To build onto manishpal's answer, I'll give some context on where to put it. (I am using Kotlin, sorry for the different syntax)
Within your application, you will probably have some kind of function that handles authentication of a user. Within my application, it is a POST endpoint for login.
#PostMapping("/login")
fun handleLogin(#RequestParam username:String?, #RequestParam password:String?) {
authenticate(username, password)
return "redirect:/"
}
fun authenticate(username:String?, password:String?) {
val user = database.findByUserName(username)
if(user.password != password) throw IllegalArgumentException()
}
Now, this is where I would add the first line of pal's answer. Spring applications have a static object called the SecurityContextHolder which you can use to access the details of the currently logged in user. In this case, we are going to set the authentication of this context ourselves.
fun authenticate(username:String?, password:String?) {
val user = database.findByUserName(username)
if (user.password != password) throw IllegalArgumentException()
val token = UsernamePasswordAuthenticationToken(user.username, user.password)
SecurityContextHolder.getContext().authentication = token
}
Now, we have set the authentication of the user in the context of this request, but it will go away after this endpoint finishes executing. We now want to set the authentication for the user across the entire session that the user is active. For this, we will have to add another parameter to our login endpoint:
#PostMapping("/login")
fun handleLogin(#RequestParam username:String?, #RequestParam password:String?, request: HttpServletRequest) {
authenticate(username, password)
request.session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext()
)
return "redirect:/"
}
This `request` parameter is automatically wired into the function call by spring, so no action is needed to include it in your HTTP request to your application.

Resources