Login/logout in REST with Spring 3 - spring

We are developing RESTful webservices with Spring 3 and we need to have the functionality of login/logout, something like /webservices/login/<username>/<password>/ and /webservices/logout. The session should be stored in the context until the session is timed out or logged out to allow consumption of other webservices. Any request to access webservices without session information should be rejected. Looking for state-of-the-art solution for this scenario.
I am actually resurrecting the question asked here Spring Security 3 programmatically login, which is still not properly answered. Please specify the changes needed in web.xml as well.

I would suggest defining your Spring Security filters completely manually. It's not that difficult, and you get full control over your login/logout behaviour.
First of all, you will need standard web.xml blurb to delegate filter chain handling to Spring (remove async-supported if you are not on Servlet API ver 3):
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<async-supported>true</async-supported>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Now, in security context you will define filters separately for each path. Filters can authenticate user, log out user, check security credentials etc.
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/login" filters="sif,wsFilter"/>
<sec:filter-chain pattern="/logout" filters="sif,logoutFilter" />
<sec:filter-chain pattern="/rest/**" filters="sif,fsi"/>
</sec:filter-chain-map>
</bean>
The XML above tells Spring to pass requests to specific context-relative URLs through filter chains. First thing in any of the filter chains is establishing security context - 'sif' bean takes care of that.
<bean id="sif" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>
Next filter in chain can now either add data to the security context (read: log in/log out user), or make a decision as to whether allow access based on said security context.
For your login URL you will want a filter that reads authentication data from the request, validates it, and in turn stores it in security context (which is stored in session):
<bean id="wsFilter" class="my.own.security.AuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="myAuthSuccessHandler"/>
<property name="passwordParameter" value="pass"></property>
<property name="usernameParameter" value="user"></property>
<property name="postOnly" value="false"></property>
You can use Spring generic UsernamePasswordAuthenticationFilter but the reason I use my own implementation is to continue filter chain processing (default implementation assumes user will get redirected on successful auth and terminates filter chain), and being able to process authentication every time username and password is passed to it:
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return ( StringUtils.hasText(obtainUsername(request)) && StringUtils.hasText(obtainPassword(request)) );
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException{
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
You can add any number of your own filter implementations for /login path, such as authentication using HTTP basic auth header, digest header, or even extract username/pwd from the request body. Spring provides a bunch of filters for that.
I have my own auth success handler who overrides the default redirect strategy:
public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#PostConstruct
public void afterPropertiesSet() {
setRedirectStrategy(new NoRedirectStrategy());
}
protected class NoRedirectStrategy implements RedirectStrategy {
#Override
public void sendRedirect(HttpServletRequest request,
HttpServletResponse response, String url) throws IOException {
// no redirect
}
}
}
You don't have to have custom auth success handler (and probably custom auth filter as well) if you're ok with user being redirected after successful login (redirect URL can be customized, check docs)
Define authentication manager who will be responsible for retrieving user's details:
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="myAuthAuthProvider"/>
</sec:authentication-manager>
<bean id="myAuthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="myUserDetailsImpl"/>
</bean>
</property>
</bean>
You will have to provide your own user details bean implementation here.
Logout filter: responsible for clearing security context
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
Generic authentication stuff:
<bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>
Access control filter (should be self-explanatory):
<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="myAuthenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="securityMetadataSource">
<sec:filter-invocation-definition-source>
<sec:intercept-url pattern="/rest/**" access="ROLE_REST"/>
</sec:filter-invocation-definition-source>
</property>
</bean>
You should also be able to secure your REST services with #Secured annotations on methods.
Context above was plucked from existing REST service webapp - sorry for any possible typos.
It is also possible to do at least most of what is implemented here by using stock sec Spring tags, but I prefer custom approach as that gives me most control.
Hope this at least gets you started.

Related

Spring Security - OAuth2 and CustomAuthenticationProvider. How to configure different URL pattern for each one?

My project has two authentication providers: Google OAuth2 client (oauth2 starter dependency) and a second custom AuthenticationProvider.
And I have two antMatcher: /api/** and /app/**.
Is it possible to authorize the /app/** with OAuth2 and /api/** with my custom authentication provider?
Because I don't want OAuth2 enabled for the REST API, but want OAuth SSO for the rest of the application.
How can I specify different URL pattern for different Authentication Provider?
Edit
Follow my configuration (Spring Boot 2.0.2):
#Configuration
#EnableWebSecurity
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.permitAll()
}
}
Tried different configurations but none worked
The AuthenticationProvider has a method: supports(Class authentication)
which accepts the authentication token, if it returns false the AuthenticationManager will not call that Provider.
Hence you could put a custom field into the Authentication Token to indicate which URI is being called, the Authentication interface has a getDetails() method that returns an Object, were you can provide the additional information.
To do this you need to create a custom AuthenticationDetails and AuthenticationDetailsSource, you could extend WebAuthenticationDetails and WebAuthenticationDetailsSource.
The WebAuthenticationDetailsSource has a buildDetails method that gives you access to the HttpServletRequest.
As you have two Authentication Providers, you need to configure two Authentication Managers. Here's a sample XML configuration for your reference:
<security:authentication-manager id="appAuthenticationManager">
<security:authentication-provider ref="appAuthenticationProvider"/>
</security:authentication-manager>
<security:authentication-manager id="apiAuthenticationManager">
<security:authentication-provider ref="apiAuthenticationProvider"/>
</security:authentication-manager>
Then configure security protection rules for endpoints.
<sec:filter-security-metadata-source id="appServerSecurityMetadataSource"
request-matcher="ant"
use-expressions="true">
<sec:intercept-url pattern="/oauth/check_token" access="isFullyAuthenticated() and hasRole('PRIVATE_SERVICE')"/>
<sec:intercept-url pattern="/oauth/token" access="isFullyAuthenticated() and hasRole('PRIVATE_SERVICE')"/>
<sec:intercept-url pattern="/oauth/jwt-token" access="isFullyAuthenticated() and hasRole('PRIVATE_SERVICE')"/>
<sec:intercept-url pattern="/**" access="denyAll()"/>
<sec:expression-handler ref="securityExpressionHandler"/>
</sec:filter-security-metadata-source>
<sec:filter-security-metadata-source id="apiServerSecurityMetadataSource"
request-matcher="ant"
use-expressions="true">
<sec:intercept-url pattern="/users/**" access="isFullyAuthenticated() and hasRole('ACTIVE_USER')"/>
<sec:intercept-url pattern="/**" access="denyAll()"/>
<sec:expression-handler ref="securityExpressionHandler"/>
</sec:filter-security-metadata-source>
Then configure filter security interceptor: (Configure similar interceptor for apiAuthenticationManager as well)
<bean id="appSecurityInterceptorFilter" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="appAuthenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="appServerSecurityMetadataSource"/>
</bean>
Last step is to register these filter beans:
<bean id="appServerSecurityFilterRegistration" class="org.springframework.boot.web.servlet.FilterRegistrationBean">
<property name="filter" ref="appSecurityInterceptorFilter"/>
<property name="enabled" value="false"/>
</bean>
Edit: To bypass some requests from entire filter chain:
Create an path matcher for all /api/** requests.
<bean id="apiRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<constructor-arg index="0" value="/api/**"/>
</bean>
Create an empty filter chain to bypass all filters for /api/** requests.
<bean id="apiFilterChain" class="org.springframework.security.web.DefaultSecurityFilterChain">
<constructor-arg name="requestMatcher" ref="apiRequestMatcher"/>
<constructor-arg name="filters">
<list/>
</constructor-arg>
</bean>
Finally, register this to filter chain proxy.
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<ref bean="apiFilterChain"/>
</list>
</constructor-arg>
</bean>
For delegating these requests to your custom provider follow the steps I shared earlier.
You can also try, <http pattern="/api/**" security="none"/> to bypass filter chain. Spring 3.1 replaced filters=”none” with a security=”none”.

How to validate user using info in headers in spring security

Currently I'm creating the web without login page.
I have another website that will send a header with info:
user:John
userCode:1234567
So my current website will check the content of the headers and validate the user in authentication manager like this:
First I create the AuthenticationEntryPoint so the unauthentication user will go there.In the AuthenticationEntryPoint I create a token and redirect the user to main page,so before its goes to the main page,spring will authenticate the user and give a token for a valid user to use the page. The code is like this:
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
if(authException.getClass().getSimpleName().equals("InsufficientAuthenticationException")) {
if (request.getHeader("user") != null) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(request.getHeader("user"), request.getHeader("userCode"));
SecurityContextHolder.getContext().setAuthentication(auth);
response.sendRedirect(request.getContextPath());
}
}
}
In the AuthenticationManager the process will go as usual and give token if the user is valid. Is there anything I need to change or another approach that can be used in spring?
Thanks!
Your case make me think of the Siteminder implementation example, in the reference documentation.
With Siteminder, a header (SM_USER) is passed with the HTTP request.
This is an example for pre-authentication in Spring Security.
Did you try this configuration ?
They begin by defining a "custom-filter" which is an instance of RequestHeaderAuthenticationFilter.
Extract of the documentation :
<security:http>
<!-- Additional http configuration omitted -->
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>

Spring 3 #Controller is not being invoked for Get request

I am attempting to use Spring 3' MVC support for annotated controllers in my web application.
In my application-context.xml, I've added the following:
<mvc:annotation-driven />
<context:component-scan base-package="com.abc.def.etc"/>
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="1" />
</bean>
My Controller is annotated as follows:
#Controller
#RequestMapping("/optimizerRules")
public class OptimizerRulesController {
private OptimizerRulesService optimizerRulesService;
private static final Log LOG = LogFactory.getLog(OptimizerRulesController.class);
public OptimizerRulesController()
{
LOG.info("Initializing OptimizerRulesController");
}
#RequestMapping(method = RequestMethod.GET)
public ModelAndView getRuleAttributesAndRules(ModelMap model)
{
LOG.info("Entering getRuleAttributesAndRules method");
}
When I start up my application I can see in my logs that the OptimizerRulesController has been initialized. I can also see the following:
Creating instance of bean 'optimizerRulesController'
Initializing OptimizerRulesController
Mapped URL path [/optimizerRules] onto handler 'optimizerRulesController'
However, when I invoke my application using http://localhost:8080/appName/optimizerRules I get a 404 error!
What configuration am I missing here?
Thanks
Spring MVC would normally log that no mapping is found for a particular request at WARN level. Assuming you're not seeing that in your logs and assuming that WARN is enabled, and since you're not seeing your own log statement, it sounds like your request isn't even hitting the Spring MVC DispatcherServlet, which probably means the URL is wrong.
The URL should be http://server:port/war/dispatcherServletMapping/optimizerRules, so your web.xml should tell you the missing path component, if my assumptions are valid.

Custom Spring Security Logout Filter

I need to de-authenticate a user (kill their session) within my spring security 3.0.5 web app and then send a redirect to another site to notify them of the logout. Is this possible within spring and if so what is the general approach to performing these tasks? Thanks!
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import com.dc.api.model.Users;
public class DCSimpleUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler{
public void onLogoutSuccess(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
Authentication authentication)
throws java.io.IOException,
javax.servlet.ServletException{
Users user=null;
Object principal = authentication.getPrincipal();
if (principal instanceof Users) {
user = (Users) principal;
if(user.getType().equals(TEST)){
response.sendRedirect("LogoutServlet");
}
}
response.sendRedirect("login.html");
}
}
java.lang.IllegalStateException
org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:463)
javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:138)
org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper.sendRedirect(SaveContextOnUpdateOrErrorResponseWrapper.java:74)
com.dc.api.service.impl.DCSimpleUrlLogoutSuccessHandler.onLogoutSuccess(DCSimpleUrlLogoutSuccessHandler.java:24)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:100)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:79)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:169)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
Actually the marked "correct answer" is about setting a custom logout success-handler, but not LogoutFilter , as defining in question.
So, if someone wants to create a custom logout filter, here is a snippet:
<bean id="securityContextLogoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<property name="filterProcessesUrl" value="/logout"/>
<constructor-arg index="0" value="/"/>
<constructor-arg index="1">
<list>
<ref bean="securityContextLogoutHandler"/>
<!--ref bean="myLogoutHandler"/-->
</list>
</constructor-arg>
</bean>
This is a default filter class with one default predefined handler (this one invalidate session).
If you really need a custom logout filter, then you should change this standard behavior (subclass this or write your own with the same interface).
Also don't forget to register it:
<security:http>
....
<custom-filter position="LOGOUT_FILTER" ref="logoutFilter"/>
</security:http>
UPDATE:
After reading some spring code, I found, that there is one more default logout handler - RememberMeServices, defined with the interface AbstractRememberMeServices implements LogoutHandler. So if you are using RememberMeServices and want to write a custom filter including RememberMe support, you also need add a reference to your RememberMeServices in list of logout handlers.
Subclass SimpleUrlLogoutSuccessHandler and override onLogoutSuccess() to do the redirect.
Configure the logout success handler like:
<http>
...
<logout success-handler-ref="myLogoutSuccessHandler"/>
</http>

How can I use Spring Security without sessions?

I am building a web application with Spring Security that will live on Amazon EC2 and use Amazon's Elastic Load Balancers. Unfortunately, ELB does not support sticky sessions, so I need to ensure my application works properly without sessions.
So far, I have setup RememberMeServices to assign a token via a cookie, and this works fine, but I want the cookie to expire with the browser session (e.g. when the browser closes).
I have to imagine I'm not the first one to want to use Spring Security without sessions... any suggestions?
In Spring Security 3 with Java Config, you can use HttpSecurity.sessionManagement():
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
We worked on the same issue (injecting a custom SecurityContextRepository to SecurityContextPersistenceFilter) for 4-5 hours today. Finally, we figured it out.
First of all, in the section 8.3 of Spring Security ref. doc, there is a SecurityContextPersistenceFilter bean definition
<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<property name='securityContextRepository'>
<bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
<property name='allowSessionCreation' value='false' />
</bean>
</property>
</bean>
And after this definition, there is this explanation:
"Alternatively you could provide a null implementation of the SecurityContextRepository interface, which will prevent the security context from being stored, even if a session has already been created during the request."
We needed to inject our custom SecurityContextRepository into the SecurityContextPersistenceFilter. So we simply changed the bean definition above with our custom impl and put it into the security context.
When we run the application, we traced the logs and saw that SecurityContextPersistenceFilter was not using our custom impl, it was using the HttpSessionSecurityContextRepository.
After a few other things we tried, we figured out that we had to give our custom SecurityContextRepository impl with the "security-context-repository-ref" attribute of "http" namespace. If you use "http" namespace and want to inject your own SecurityContextRepository impl, try "security-context-repository-ref" attribute.
When "http" namespace is used, a seperate SecurityContextPersistenceFilter definition is ignored. As I copied above, the reference doc. does not state that.
Please correct me if I misunderstood the things.
It seems to be even easier in Spring Securitiy 3.0. If you're using namespace configuration, you can simply do as follows:
<http create-session="never">
<!-- config -->
</http>
Or you could configure the SecurityContextRepository as null, and nothing would ever get saved that way as well.
Take a look at SecurityContextPersistenceFilter class. It defines how the SecurityContextHolder is populated. By default it uses HttpSessionSecurityContextRepository to store security context in http session.
I have implemented this mechanism quite easily, with custom SecurityContextRepository.
See the securityContext.xml below:
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
<context:annotation-config/>
<sec:global-method-security secured-annotations="enabled" pre-post-annotations="enabled"/>
<bean id="securityContextRepository" class="com.project.server.security.TokenSecurityContextRepository"/>
<bean id="securityContextFilter" class="com.project.server.security.TokenSecurityContextPersistenceFilter">
<property name="repository" ref="securityContextRepository"/>
</bean>
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="/login.jsp"/>
<constructor-arg>
<list>
<bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
<bean id="formLoginFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler">
<bean class="com.project.server.security.TokenAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/index.html"/>
<property name="passwordExpiredUrl" value="/changePassword.jsp"/>
<property name="alwaysUseDefaultTargetUrl" value="true"/>
</bean>
</property>
<property name="authenticationFailureHandler">
<bean class="com.project.server.modules.security.CustomUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login.jsp?failure=1"/>
</bean>
</property>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="allowSessionCreation" value="false"/>
</bean>
<bean id="servletApiFilter"
class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>
<bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="ClientApplication"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>
<bean id="exceptionTranslator" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/login.jsp?failure=2"/>
</bean>
</property>
<property name="requestCache">
<bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
</property>
</bean>
<alias name="filterChainProxy" alias="springSecurityFilterChain"/>
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/**"
filters="securityContextFilter, logoutFilter, formLoginFilter,
servletApiFilter, anonFilter, exceptionTranslator, filterSecurityInterceptor"/>
</sec:filter-chain-map>
</bean>
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="securityMetadataSource">
<sec:filter-security-metadata-source use-expressions="true">
<sec:intercept-url pattern="/staticresources/**" access="permitAll"/>
<sec:intercept-url pattern="/index.html*" access="hasRole('USER_ROLE')"/>
<sec:intercept-url pattern="/rpc/*" access="hasRole('USER_ROLE')"/>
<sec:intercept-url pattern="/**" access="permitAll"/>
</sec:filter-security-metadata-source>
</property>
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
</bean>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
</list>
</property>
</bean>
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<bean name="authenticationProvider"
class="com.project.server.modules.security.oracle.StoredProcedureBasedAuthenticationProviderImpl">
<property name="dataSource" ref="serverDataSource"/>
<property name="userDetailsService" ref="userDetailsService"/>
<property name="auditLogin" value="true"/>
<property name="postAuthenticationChecks" ref="customPostAuthenticationChecks"/>
</bean>
</list>
</property>
</bean>
<bean id="customPostAuthenticationChecks" class="com.project.server.modules.security.CustomPostAuthenticationChecks"/>
<bean name="userDetailsService" class="com.project.server.modules.security.oracle.UserDetailsServiceImpl">
<property name="dataSource" ref="serverDataSource"/>
</bean>
</beans>
Actually create-session="never" doesn't mean being completely stateless. There's an issue for that in Spring Security issue management.
EDIT: As of Spring Security 3.1, there is a STATELESS option that can be used instead of all this. See the other answers. Original answer kept below for posterity.
After struggling with the numerous solutions posted in this answer, to try to get something working when using the <http> namespace config, I finally found an approach that actually works for my use case. I don't actually require that Spring Security doesn't start a session (because I use session in other parts of the application), just that it doesn't "remember" authentication in the session at all (it should be re-checked every request).
To begin with, I wasn't able to figure out how to do the "null implementation" technique described above. It wasn't clear whether you are supposed to set the securityContextRepository to null or to a no-op implementation. The former does not work because a NullPointerException gets thrown within SecurityContextPersistenceFilter.doFilter(). As for the no-op implementation, I tried implementing in the simplest way I could imagine:
public class NullSpringSecurityContextRepository implements SecurityContextRepository {
#Override
public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder_) {
return SecurityContextHolder.createEmptyContext();
}
#Override
public void saveContext(final SecurityContext context_, final HttpServletRequest request_,
final HttpServletResponse response_) {
}
#Override
public boolean containsContext(final HttpServletRequest request_) {
return false;
}
}
This doesn't work in my application, because of some strange ClassCastException having to do with the response_ type.
Even assuming I did manage to find an implementation that works (by simply not storing the context in session), there is still the problem of how to inject that into the filters built by the <http> configuration. You cannot simply replace the filter at the SECURITY_CONTEXT_FILTER position, as per the docs. The only way I found to hook into the SecurityContextPersistenceFilter that is created under the covers was to write an ugly ApplicationContextAware bean:
public class SpringSecuritySessionDisabler implements ApplicationContextAware {
private final Logger logger = LoggerFactory.getLogger(SpringSecuritySessionDisabler.class);
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(final ApplicationContext applicationContext_) throws BeansException {
applicationContext = applicationContext_;
}
public void disableSpringSecuritySessions() {
final Map<String, FilterChainProxy> filterChainProxies = applicationContext
.getBeansOfType(FilterChainProxy.class);
for (final Entry<String, FilterChainProxy> filterChainProxyBeanEntry : filterChainProxies.entrySet()) {
for (final Entry<String, List<Filter>> filterChainMapEntry : filterChainProxyBeanEntry.getValue()
.getFilterChainMap().entrySet()) {
final List<Filter> filterList = filterChainMapEntry.getValue();
if (filterList.size() > 0) {
for (final Filter filter : filterList) {
if (filter instanceof SecurityContextPersistenceFilter) {
logger.info(
"Found SecurityContextPersistenceFilter, mapped to URL '{}' in the FilterChainProxy bean named '{}', setting its securityContextRepository to the null implementation to disable caching of authentication",
filterChainMapEntry.getKey(), filterChainProxyBeanEntry.getKey());
((SecurityContextPersistenceFilter) filter).setSecurityContextRepository(
new NullSpringSecurityContextRepository());
}
}
}
}
}
}
}
Anyway, to the solution that actually does work, albeit very hackish. Simply use a Filter that deletes the session entry that the HttpSessionSecurityContextRepository looks for when it does its thing:
public class SpringSecuritySessionDeletingFilter extends GenericFilterBean implements Filter {
#Override
public void doFilter(final ServletRequest request_, final ServletResponse response_, final FilterChain chain_)
throws IOException, ServletException {
final HttpServletRequest servletRequest = (HttpServletRequest) request_;
final HttpSession session = servletRequest.getSession();
if (session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY) != null) {
session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
}
chain_.doFilter(request_, response_);
}
}
Then in the configuration:
<bean id="springSecuritySessionDeletingFilter"
class="SpringSecuritySessionDeletingFilter" />
<sec:http auto-config="false" create-session="never"
entry-point-ref="authEntryPoint">
<sec:intercept-url pattern="/**"
access="IS_AUTHENTICATED_REMEMBERED" />
<sec:intercept-url pattern="/static/**" filters="none" />
<sec:custom-filter ref="myLoginFilterChain"
position="FORM_LOGIN_FILTER" />
<sec:custom-filter ref="springSecuritySessionDeletingFilter"
before="SECURITY_CONTEXT_FILTER" />
</sec:http>
Just a quick note: it's "create-session" rather than "create-sessions"
create-session
Controls the eagerness with which an HTTP session is created.
If not set, defaults to "ifRequired". Other options are "always" and "never".
The setting of this attribute affect the allowSessionCreation and forceEagerSessionCreation properties of HttpSessionContextIntegrationFilter. allowSessionCreation will always be true unless this attribute is set to "never". forceEagerSessionCreation is "false" unless it is set to "always".
So the default configuration allows session creation but does not force it. The exception is if concurrent session control is enabled, when forceEagerSessionCreation will be set to true, regardless of what the setting is here. Using "never" would then cause an exception during the initialization of HttpSessionContextIntegrationFilter.
For specific details of the session usage, there is some good documentation in the HttpSessionSecurityContextRepository javadoc.
Now ELB supports sticky sessions, I think from 2016.
But also it's possible to store your sessions in Redis.

Resources