diff --git a/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java index d01faac026..da8ee38f67 100644 --- a/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java @@ -120,7 +120,7 @@ public class FormLoginBeanDefinitionParser { } if (sessionStrategy != null) { - filterBuilder.addPropertyValue("authenticatedSessionStrategy", sessionStrategy); + filterBuilder.addPropertyValue("sessionAuthenticationStrategy", sessionStrategy); } if (StringUtils.hasText(failureHandlerRef)) { diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 7b94090abf..9a387709e2 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -41,7 +41,7 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa import org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextPersistenceFilter; -import org.springframework.security.web.session.ConcurrentSessionControlAuthenticatedSessionStrategy; +import org.springframework.security.web.session.ConcurrentSessionControlStrategy; import org.springframework.security.web.session.DefaultSessionAuthenticationStrategy; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.util.AntUrlPathMatcher; @@ -71,7 +71,8 @@ class HttpConfigurationBuilder { private static final String OPT_SESSION_FIXATION_MIGRATE_SESSION = "migrateSession"; private static final String ATT_INVALID_SESSION_URL = "invalid-session-url"; - + private static final String ATT_SESSION_AUTH_STRATEGY_REF = "session-authentication-strategy-ref"; + private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url"; private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref"; private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting"; @@ -197,29 +198,41 @@ class HttpConfigurationBuilder { String sessionFixationAttribute = null; String invalidSessionUrl = null; + String sessionAuthStratRef = null; + String errorUrl = null; if (sessionMgmtElt != null) { sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION); invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL); + sessionAuthStratRef = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF); + errorUrl = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_ERROR_URL); sessionCtrlElt = DomUtils.getChildElementByTagName(sessionMgmtElt, Elements.CONCURRENT_SESSIONS); if (sessionCtrlElt != null) { + if (StringUtils.hasText(sessionAuthStratRef)) { + pc.getReaderContext().error(ATT_SESSION_AUTH_STRATEGY_REF + " attribute cannot be used" + + " in combination with <" + Elements.CONCURRENT_SESSIONS + ">", pc.extractSource(sessionCtrlElt)); + } createConcurrencyControlFilterAndSessionRegistry(sessionCtrlElt); } } if (!StringUtils.hasText(sessionFixationAttribute)) { + if (StringUtils.hasText(sessionAuthStratRef)) { + pc.getReaderContext().error(ATT_SESSION_FIXATION_PROTECTION + " attribute cannot be used" + + " in combination with " + ATT_SESSION_AUTH_STRATEGY_REF, pc.extractSource(sessionCtrlElt)); + } + sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION; } boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION); BeanDefinitionBuilder sessionStrategy; - String concurrencyErrorUrl = null; if (sessionCtrlElt != null) { assert sessionRegistryRef != null; - sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticatedSessionStrategy.class); + sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlStrategy.class); sessionStrategy.addConstructorArgValue(sessionRegistryRef); String maxSessions = sessionCtrlElt.getAttribute("max-sessions"); @@ -232,10 +245,9 @@ class HttpConfigurationBuilder { if (StringUtils.hasText(exceptionIfMaximumExceeded)) { sessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded); - - concurrencyErrorUrl = sessionCtrlElt.getAttribute("error-url"); } - } else if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)) { + } else if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl) + || StringUtils.hasText(sessionAuthStratRef)) { sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(DefaultSessionAuthenticationStrategy.class); } else { sfpf = null; @@ -244,28 +256,31 @@ class HttpConfigurationBuilder { BeanDefinitionBuilder sessionMgmtFilter = BeanDefinitionBuilder.rootBeanDefinition(SessionManagementFilter.class); RootBeanDefinition failureHandler = new RootBeanDefinition(SimpleUrlAuthenticationFailureHandler.class); - if (StringUtils.hasText(concurrencyErrorUrl)) { - failureHandler.getPropertyValues().addPropertyValue("defaultFailureUrl", concurrencyErrorUrl); + if (StringUtils.hasText(errorUrl)) { + failureHandler.getPropertyValues().addPropertyValue("defaultFailureUrl", errorUrl); } sessionMgmtFilter.addPropertyValue("authenticationFailureHandler", failureHandler); sessionMgmtFilter.addConstructorArgValue(contextRepoRef); - BeanDefinition strategyBean = sessionStrategy.getBeanDefinition(); - String sessionStrategyId = pc.getReaderContext().registerWithGeneratedName(strategyBean); - pc.registerBeanComponent(new BeanComponentDefinition(strategyBean, sessionStrategyId)); - sessionMgmtFilter.addPropertyReference("authenticatedSessionStrategy", sessionStrategyId); - if (sessionFixationProtectionRequired) { + if (!StringUtils.hasText(sessionAuthStratRef)) { + BeanDefinition strategyBean = sessionStrategy.getBeanDefinition(); - sessionStrategy.addPropertyValue("migrateSessionAttributes", - Boolean.valueOf(sessionFixationAttribute.equals(OPT_SESSION_FIXATION_MIGRATE_SESSION))); + if (sessionFixationProtectionRequired) { + sessionStrategy.addPropertyValue("migrateSessionAttributes", + Boolean.valueOf(sessionFixationAttribute.equals(OPT_SESSION_FIXATION_MIGRATE_SESSION))); + } + sessionAuthStratRef = pc.getReaderContext().registerWithGeneratedName(strategyBean); + pc.registerBeanComponent(new BeanComponentDefinition(strategyBean, sessionAuthStratRef)); } if (StringUtils.hasText(invalidSessionUrl)) { sessionMgmtFilter.addPropertyValue("invalidSessionUrl", invalidSessionUrl); } + sessionMgmtFilter.addPropertyReference("sessionAuthenticationStrategy", sessionAuthStratRef); + sfpf = (RootBeanDefinition) sessionMgmtFilter.getBeanDefinition(); - sessionStrategyRef = new RuntimeBeanReference(sessionStrategyId); + sessionStrategyRef = new RuntimeBeanReference(sessionAuthStratRef); } private void createConcurrencyControlFilterAndSessionRegistry(Element element) { @@ -399,9 +414,6 @@ class HttpConfigurationBuilder { return channelRequestMap; } - - - void createFilterSecurityInterceptor(BeanReference authManager) { BeanDefinitionBuilder fidsBuilder; diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.0.rnc index 3ffd4e4306..676ec5d289 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.0.rnc @@ -425,6 +425,12 @@ session-management.attlist &= session-management.attlist &= ## The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts. attribute invalid-session-url {xsd:token}? +session-management.attlist &= + ## Allows injection of the SessionAuthenticationStrategy instance used by the SessionManagementFilter + attribute session-authentication-strategy-ref {xsd:token}? +session-management.attlist &= + ## Defines the URL of the error page which should be shown when the SessionAuthenticationStrategy raises an exception. If not set, an unauthorized (402) error code will be returned to the client. Note that this attribute doesn't apply if the error occurs during a form-based login, where the URL for authentication failure will take precedence. + attribute session-authentication-error-url {xsd:token}? concurrency-control = @@ -438,11 +444,8 @@ concurrency-control.attlist &= ## The URL a user will be redirected to if they attempt to use a session which has been "expired" because they have logged in again. attribute expired-url {xsd:token}? concurrency-control.attlist &= - ## Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. + ## Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. If the session-authentication-error-url attribute is set on the session-management URL, the user will be redirected to this URL. attribute error-if-maximum-exceeded {boolean}? -concurrency-control.attlist &= - ## Defines the URL of the error page which should be shown when the maximum is exceeded and error-if-maximum-exceeded is 'true'. If not set, an unauthorized (402) error code will be returned to the client. Note that this attribute doesn't apply if the error occurs during a form-based login, where the URL for authentication failure will take precedence. - attribute error-url {xsd:token}? concurrency-control.attlist &= ## Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration. attribute session-registry-alias {xsd:token}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.0.xsd index 6ce574039e..e792ce6748 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.0.xsd @@ -953,6 +953,16 @@ The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts. + + + Allows injection of the SessionAuthenticationStrategy instance used by the SessionManagementFilter + + + + + Defines the URL of the error page which should be shown when the SessionAuthenticationStrategy raises an exception. If not set, an unauthorized (402) error code will be returned to the client. Note that this attribute doesn't apply if the error occurs during a form-based login, where the URL for authentication failure will take precedence. + + @@ -968,12 +978,7 @@ - Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. - - - - - Defines the URL of the error page which should be shown when the maximum is exceeded and error-if-maximum-exceeded is 'true'. If not set, an unauthorized (402) error code will be returned to the client. + Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. If the session-authentication-error-url attribute is set on the session-management URL, the user will be redirected to this URL. diff --git a/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java index 2c48ce587e..b22dac8ea7 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java @@ -706,22 +706,22 @@ public class HttpSecurityBeanDefinitionParserTests { public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception { setContext( "" + - " " + - " " + + " " + + " " + " " + "" + AUTH_PROVIDER_XML); - SessionManagementFilter seshStrategy = (SessionManagementFilter) getFilter(SessionManagementFilter.class); + SessionManagementFilter seshFilter = (SessionManagementFilter) getFilter(SessionManagementFilter.class); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass"); SecurityContextHolder.getContext().setAuthentication(auth); // Register 2 sessions and then check a third // req.setSession(new MockHttpSession()); // auth.setDetails(new WebAuthenticationDetails(req)); MockHttpServletResponse response = new MockHttpServletResponse(); - seshStrategy.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()); + seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()); assertNull(response.getRedirectedUrl()); - seshStrategy.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()); + seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()); assertNull(response.getRedirectedUrl()); - seshStrategy.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()); + seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()); assertEquals("/max-exceeded", response.getRedirectedUrl()); } diff --git a/docs/manual/src/docbook/appendix-namespace.xml b/docs/manual/src/docbook/appendix-namespace.xml index 3b954e5c27..26733a136f 100644 --- a/docs/manual/src/docbook/appendix-namespace.xml +++ b/docs/manual/src/docbook/appendix-namespace.xml @@ -17,16 +17,15 @@ The <http> element encapsulates the security configuration for the web layer of your application. It creates a FilterChainProxy bean named "springSecurityFilterChain" which maintains the stack of security filters which make up - the web security configuration - See the introductory chapter for how to set up - the mapping from your web.xml - . Some core filters are always created and others will be added to the stack - depending on the attributes child elements which are present. The positions of the standard - filters are fixed (see the filter order table in the - namespace introduction), removing a common source of errors with previous versions of the - framework when users had to configure the filter chain explicitly in - theFilterChainProxy bean. You can, of course, still do this if you - need full control of the configuration. + the web security configuration See the + introductory chapter for how to set up the mapping from your + web.xml. Some core filters are always created and + others will be added to the stack depending on the attributes child elements which are + present. The positions of the standard filters are fixed (see the filter order table in the namespace introduction), removing a common source of + errors with previous versions of the framework when users had to configure the filter chain + explicitly in theFilterChainProxy bean. You can, of course, still do + this if you need full control of the configuration. All filters which require a reference to the AuthenticationManager will be automatically injected with the internal instance created by the namespace configuration (see the Whether test URLs should be converted to lower case prior to comparing with defined path patterns. If unspecified, defaults to "true" -
- <literal>session-fixation-protection</literal> - Indicates whether an existing session should be invalidated when a user authenticates - and a new session started. If set to "none" no change will be made. "newSession" will - create a new empty session. "migrateSession" will create a new session and copy the - session attributes to the new session. Defaults to "migrateSession". - If enabled this will add a SessionFixationProtectionFilter to - the stack. The session fixation protection options on namespace-created instances of - AbstractAuthenticationProcessingFilter will also be set - appropriately. -
<literal>realm</literal> Sets the realm name used for basic authentication (if enabled). Corresponds to the @@ -124,8 +112,9 @@ default AccessDeniedHandler used by the ExceptionTranslationFilter, (using the error-page attribute, or to supply your own implementation using the - ref attribute. This is discussed in more detail in the section on - the ExceptionTranslationFilter. + ref attribute. This is discussed in more detail in the section on the + ExceptionTranslationFilter.
The <literal><intercept-url></literal> Element @@ -161,7 +150,8 @@ there is no preference. If this attribute is present on any <intercept-url> element, then a ChannelProcessingFilter will be added to the filter stack and its - additional dependencies added to the application context. If a <port-mappings> configuration is added, this will be @@ -185,14 +175,13 @@ the filter stack and an LoginUrlAuthenticationEntryPoint to the application context to provide authentication on demand. This will always take precedence over other namespace-created entry points. If no attributes are supplied, a login page will - be generated automatically at the URL "/spring-security-login" - This feature is really just provided for convenience and is not intended for - production (where a view technology will have been chosen and can be used to render a - customized login page). The class - DefaultLoginPageGeneratingFilter is responsible for rendering - the login page and will provide login forms for both normal form login and/or OpenID if - required. - The behaviour can be customized using the following attributes. + be generated automatically at the URL "/spring-security-login" This feature + is really just provided for convenience and is not intended for production (where a view + technology will have been chosen and can be used to render a customized login page). The + class DefaultLoginPageGeneratingFilter is responsible for + rendering the login page and will provide login forms for both normal form login and/or + OpenID if required. The behaviour can be customized using the + following attributes.
<literal>login-page</literal> The URL that should be used to render the login page. Maps to the @@ -232,18 +221,18 @@
<literal>authentication-success-handler-ref</literal> - This can be used as an alternative to default-target-url - and always-use-default-target, giving you full control over the navigation flow - after a successful authentication. The value should be he name of an AuthenticationSuccessHandler - bean in the application context. - + This can be used as an alternative to default-target-url and + always-use-default-target, giving you full control over the + navigation flow after a successful authentication. The value should be he name of an + AuthenticationSuccessHandler bean in the application + context.
<literal>authentication-failure-handler-ref</literal> - Can be used as an alternative to authentication-failure-url, giving you full control over the navigation flow - after an authentication failure. The value should be he name of an AuthenticationFailureHandler - bean in the application context. - + Can be used as an alternative to authentication-failure-url, giving + you full control over the navigation flow after an authentication failure. The value + should be he name of an AuthenticationFailureHandler bean + in the application context.
@@ -286,11 +275,9 @@ The <literal>key</literal> Attribute Maps to the "key" property of AbstractRememberMeServices. Should be set to a unique value to ensure that remember-me cookies are only valid within - the one application - This doesn't affect the use of + the one application This doesn't affect the use of PersistentTokenBasedRememberMeServices, where the tokens are - stored on the server side. - . + stored on the server side..
<literal>token-validity-seconds</literal> @@ -308,21 +295,33 @@ explicitly using this attribute.
+
+ The <literal><session-management></literal> Element + Session-management related functionality is implemented by the addition of a + SessionManagementFilter to the filter stack. +
+ <literal>session-fixation-protection</literal> + Indicates whether an existing session should be invalidated when a user authenticates + and a new session started. If set to "none" no change will be made. "newSession" will + create a new empty session. "migrateSession" will create a new session and copy the + session attributes to the new session. Defaults to "migrateSession". + + If session fixation protection is enabled, the SessionManagementFilter + is inected with a appropriately configured DefaultSessionAuthenticationStrategy. + See the Javadoc for this class for more details. + +
+
- The <literal><concurrent-session-control></literal> Element + The <literal><concurrency-control></literal> Element Adds support for concurrent session control, allowing limits to be placed on the number of active sessions a user can have. A ConcurrentSessionFilter will be - created, along with a ConcurrentSessionControllerImpl and an instance - of SessionRegistry (a + created, and a ConcurrentSessionControlStrategy will be used with the + SessionManagementFilter. If a form-login + element has been declared, the strategy object will also be injected into the created + authentication filter. An instance of SessionRegistry (a SessionRegistryImpl instance unless the user wishes to use a custom - bean). The controller is registered with the namespace's - AuthenticationManager - (ProviderManager). Other namespace-created beans which require a - reference to the SessionRegistry will automatically have it - injected. - Note that the forceEagerSessionCreation of - HttpSessionContextIntegrationFilter will be set to - true if concurrent session control is in use. + bean) will be created for use by the strategy.
The <literal>max-sessions</literal> attribute Maps to the maximumSessions property of @@ -337,10 +336,10 @@ expiry message will just be written directly back to the response.
- The <literal>exception-if-maximum-exceeded</literal> attribute - If set to "true" a ConcurrentLoginException should be - raised when a user attempts to exceed the maximum allowed number of sessions. The default - behaviour is to expire the original session. + The <literal>error-if-maximum-exceeded</literal> attribute + If set to "true" a SessionAuthenticationException will + be raised when a user attempts to exceed the maximum allowed number of sessions. The + default behaviour is to expire the original session.
The <literal>session-registry-alias</literal> and @@ -438,7 +437,8 @@ <section> <title>The <literal><authentication-provider></literal> Element This element is basically a shorthand syntax for configuring a DaoAuthenticationProvider. + xlink:href="#core-services-dao-provider" + >DaoAuthenticationProvider. DaoAuthenticationProvider loads user information from a UserDetailsService and compares the username/password combination with the values supplied at login. The @@ -447,15 +447,15 @@ user-service-ref attribute to point to a bean defined elsewhere in the application context). You can find examples of these variations in the namespace introduction. -
- The <literal><password-encoder></literal> Element - Authentication providers can optionally be configured to use a password encoder as - described in the namespace introduction. - This will result in the bean being injected with the appropriate PasswordEncoder - instance, potentially with an accompanying SaltSource bean to - provide salt values for hashing. - -
+
+ The <literal><password-encoder></literal> Element + Authentication providers can optionally be configured to use a password encoder as + described in the namespace introduction. + This will result in the bean being injected with the appropriate + PasswordEncoder instance, potentially with an + accompanying SaltSource bean to provide salt values for + hashing. +
Using <literal><authentication-provider></literal> to refer to an @@ -497,15 +497,11 @@ <para> Rather than defining security attributes on an individual method or class basis using the <literal>@Secured</literal> annotation, you can define cross-cutting security constraints across whole sets of methods and interfaces in your service layer using the - <literal><protect-pointcut></literal> element. This has two attributes: <itemizedlist> - <listitem> - <para><literal>expression</literal> - the pointcut expression</para> - </listitem> - <listitem> - <para><literal>access</literal> - the security attributes which apply</para> - </listitem> - </itemizedlist> You can find an example in the <link xlink:href="#ns-protect-pointcut" - >namespace introduction</link>. </para> + <literal><protect-pointcut></literal> element. This has two attributes: + <itemizedlist><listitem><para><literal>expression</literal> - the pointcut + expression</para></listitem><listitem><para><literal>access</literal> - the security + attributes which apply</para></listitem></itemizedlist> You can find an example in + the <link xlink:href="#ns-protect-pointcut">namespace introduction</link>. </para> </section> <section xml:id="nsa-custom-after-invocation"> <title>The <literal><after-invocation-provider></literal> Element diff --git a/docs/manual/src/docbook/concurrent-sessions.xml b/docs/manual/src/docbook/concurrent-sessions.xml index cd9ef4fa2c..755da3986a 100644 --- a/docs/manual/src/docbook/concurrent-sessions.xml +++ b/docs/manual/src/docbook/concurrent-sessions.xml @@ -1,15 +1,65 @@ - Session Management - + HTTP session related functonality is handled by a combination of the + SessionManagementFilter and the + SessionAuthenticationStrategy interface, which the filter + delegates to. Typical usage includes session-fixation protection attack prevention, detection of + session timeouts and restrictions on how many sessions an authenticated user may have open + concurrently.
SessionManagementFilter - HTTP session related functonality is handled by the - SessionManagementFilter. This + The SessionManagementFilter checks the contents of the + SecurityContextRepository against the current contents of the + SecurityContextHolder to deterine whether a user has been + authenticated during the current request, typically by a non-interactive authentication mechanism, such + as pre-authentication or remember-me Authentication by mechanisms which perform a redirect + after authenticating (such as form-login) will not be detected by SessionManagementFilter, + as the filter will not be invoked during the authenticating request. Session-management functionality has to be + handled separately in these cases. + . + If the repository contains a security context, the + filter does nothing. If it doesn't, and the thread-local + SecurityContext contains a (non-anonymous) + Authentication object, the filter assumes they have been + authenticated by a previous filter in the stack. It will then invoke the configured + SessionAuthenticationStrategy. + If the user is not currently authenticated, the filter will check whether an invalid + session ID has been requested (because of a timeout, for example) and will redirect to the + configured invalidSessionUrl if set. The easiest way to configure this is + through the namespace, as described earlier.
+ <interfacename>SessionAuthenticationStrategy</interfacename> + + SessionAuthenticationStrategy is used by both SessionManagementFilter + and AbstractAutheticationProcessingFilter, so if you are using a customized form-login class, for example, you will need to inject + it into both of these. In this case, a typical configuration, combining the namespace and custom beans might look like + + + + + + + + ... + + + + + + +]]> + + + +
+
Concurrency Control Spring Security is able to prevent a principal from concurrently authenticating to the same application more than a specified number of times. Many ISVs take advantage of this to @@ -18,14 +68,17 @@ the web application from two different sessions. This feature is supported by the namespace, so please check the earlier namespace chapter for the simplest configuration. Sometimes you need to customize things though. - The implementation has changed substantially in Spring Security 3. Previously the - concurrent authentication check was made by the ProviderManager, which - could be injected with a ConcurrentSessionController which would check if - the user was attempting to exceed the number of sessions permitted. However, this approach - required that an HTTP session be created in advance, which is undesirable. In Spring Security - 3, the user is first authenticated by the AuthenticationManager - and once they are successfully authenticated, a session is created and the check is made - whether they are allowed to have another session open. + The implementation uses a specialized version of + SessionAuthenticationStrategy, called + ConcurrentSessionControlStrategy. + Previously the concurrent authentication check was made by the + ProviderManager, which could be injected with a + ConcurrentSessionController which would check if the user was + attempting to exceed the number of sessions permitted. However, this approach required that + an HTTP session be created in advance, which is undesirable. In Spring Security 3, the user + is first authenticated by the AuthenticationManager and once + they are successfully authenticated, a session is created and the check is made whether they + are allowed to have another session open. To use concurrent session support, you'll need to add the following to web.xml: @@ -33,40 +86,50 @@ org.springframework.security.web.session.HttpSessionEventPublisher ]]> - + In addition, you will need to add the org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter to your FilterChainProxy. The ConcurrentSessionFilter requires two properties, sessionRegistry, which generally points to an instance of SessionRegistryImpl, and expiredUrl, which points to - the page to display when a session has expired. - The web.xml - HttpSessionEventPublisher causes an ApplicationEvent to - be published to the Spring ApplicationContext every time a - HttpSession commences or terminates. This is critical, as it allows the - SessionRegistryImpl to be notified when a session ends. - You will also need to wire up the ConcurrentSessionControllerImpl - and refer to it from your ProviderManager bean: - - - - - - - - - - - - - - -]]> + the page to display when a session has expired. A configuration using the namespace to create the + FilterChainProxy and other default beans might look like this: + + + + + + + + + + + + + + + ... + + + + + + + + +]]> + + Adding the listener to web.xml causes an ApplicationEvent to + be published to the Spring ApplicationContext every time a + HttpSession commences or terminates. This is critical, as it allows the + SessionRegistryImpl to be notified when a session ends. Without it, a user + will never be able to log back in again once they have exceeded their session allowance, even if they log out + of another session or it times out.
diff --git a/docs/manual/src/docbook/namespace-config.xml b/docs/manual/src/docbook/namespace-config.xml index d2f5645928..e20b303e99 100644 --- a/docs/manual/src/docbook/namespace-config.xml +++ b/docs/manual/src/docbook/namespace-config.xml @@ -355,19 +355,31 @@ in
-
+
Session Management - If you wish to place constraints on a single user's ability to log in to your - application, Spring Security supports this out of the box with the following simple - additions. First you need to add the following listener to your web.xml - file to keep Spring Security updated about session lifecycle events: - + Detecting Timeouts + You can configure Spring Security to detect the submission of an invalid session ID + and redirect the user to an appropriate URL. This is achieved through the + session-management element: + ... + + ]]> +
+
+ Concurrent Session Control + If you wish to place constraints on a single user's ability to log in to your + application, Spring Security supports this out of the box with the following simple + additions. First you need to add the following listener to your + web.xml file to keep Spring Security updated about session + lifecycle events: org.springframework.security.web.session.HttpSessionEventPublisher -]]> Then add the following lines to your application context: Then add the following lines to your application context: ... @@ -375,8 +387,8 @@ ]]> This will prevent a user from logging in multiple times - a - second login will cause the first to be invalidated. Often you would prefer to prevent a - second login, in which case you can use ... @@ -384,13 +396,17 @@ ]]> The second login will then be rejected. By - rejected, we mean that the user will be sent to the - authentication-failure-url if form-based login is being used. If the - second authentication takes place through another non-interactive mechanism, such as - remember-me, an unauthorized (402) error will be sent to the - client. If instead you want to use an error page, you can add the attribute - error-url to the concurrency-control - element. + rejected, we mean that the user will be sent to the + authentication-failure-url if form-based login is being used. If the + second authentication takes place through another non-interactive mechanism, such as + remember-me, an unauthorized (402) error will be sent to + the client. If instead you want to use an error page, you can add the attribute + session-authentication-error-url to the + session-management element. + If you are using a customized authentication filter for form-based login, then you + have to configure concurrent session control support explicitly. More details can be found + in the Session Management chapter. +
OpenID Login diff --git a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java index 17100fc55c..caa8d22172 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java @@ -400,7 +400,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt * @param sessionStrategy the implementation to use. If not set a null implementation is * used. */ - public void setAuthenticatedSessionStrategy(SessionAuthenticationStrategy sessionStrategy) { + public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) { this.sessionStrategy = sessionStrategy; } diff --git a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionControlAuthenticatedSessionStrategy.java b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionControlStrategy.java similarity index 96% rename from web/src/main/java/org/springframework/security/web/session/ConcurrentSessionControlAuthenticatedSessionStrategy.java rename to web/src/main/java/org/springframework/security/web/session/ConcurrentSessionControlStrategy.java index 5533971a44..35ef91cefe 100644 --- a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionControlAuthenticatedSessionStrategy.java +++ b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionControlStrategy.java @@ -22,7 +22,7 @@ import org.springframework.util.Assert; * @version $Id$ * @since 3.0 */ -public class ConcurrentSessionControlAuthenticatedSessionStrategy extends DefaultSessionAuthenticationStrategy +public class ConcurrentSessionControlStrategy extends DefaultSessionAuthenticationStrategy implements MessageSourceAware { protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private final SessionRegistry sessionRegistry; @@ -32,7 +32,7 @@ public class ConcurrentSessionControlAuthenticatedSessionStrategy extends Defaul /** * @param sessionRegistry the session registry which should be updated when the authenticated session is changed. */ - public ConcurrentSessionControlAuthenticatedSessionStrategy(SessionRegistry sessionRegistry) { + public ConcurrentSessionControlStrategy(SessionRegistry sessionRegistry) { Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null"); super.setAlwaysCreateSession(true); this.sessionRegistry = sessionRegistry; diff --git a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java index c0753ecd5a..e8e5f0aeea 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java @@ -98,7 +98,7 @@ public class SessionManagementFilter extends GenericFilterBean { * * @param sessionStrategy the strategy object. If not set, a {@link DefaultSessionAuthenticationStrategy} is used. */ - public void setAuthenticatedSessionStrategy(SessionAuthenticationStrategy sessionStrategy) { + public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) { Assert.notNull(sessionStrategy, "authenticatedSessionStratedy must not be null"); this.sessionStrategy = sessionStrategy; } diff --git a/web/src/test/java/org/springframework/security/web/authentication/AbstractProcessingFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/AbstractProcessingFilterTests.java index e34aace005..34891960ee 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/AbstractProcessingFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/AbstractProcessingFilterTests.java @@ -240,7 +240,7 @@ public class AbstractProcessingFilterTests extends TestCase { MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true); filter.setFilterProcessesUrl("/j_mock_post"); - filter.setAuthenticatedSessionStrategy(mock(SessionAuthenticationStrategy.class)); + filter.setSessionAuthenticationStrategy(mock(SessionAuthenticationStrategy.class)); filter.setAuthenticationSuccessHandler(successHandler); filter.setAuthenticationFailureHandler(failureHandler); filter.setAuthenticationManager(mock(AuthenticationManager.class)); diff --git a/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java b/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java index e5a3eb9431..603b61d13d 100644 --- a/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java @@ -48,7 +48,7 @@ public class SessionManagementFilterTests { // mock that repo contains a security context when(repo.containsContext(any(HttpServletRequest.class))).thenReturn(true); SessionManagementFilter filter = new SessionManagementFilter(repo); - filter.setAuthenticatedSessionStrategy(strategy); + filter.setSessionAuthenticationStrategy(strategy); HttpServletRequest request = new MockHttpServletRequest(); authenticateUser(); @@ -62,7 +62,7 @@ public class SessionManagementFilterTests { SecurityContextRepository repo = mock(SecurityContextRepository.class); SessionAuthenticationStrategy strategy = mock(SessionAuthenticationStrategy.class); SessionManagementFilter filter = new SessionManagementFilter(repo); - filter.setAuthenticatedSessionStrategy(strategy); + filter.setSessionAuthenticationStrategy(strategy); HttpServletRequest request = new MockHttpServletRequest(); filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); @@ -76,7 +76,7 @@ public class SessionManagementFilterTests { // repo will return false to containsContext() SessionAuthenticationStrategy strategy = mock(SessionAuthenticationStrategy.class); SessionManagementFilter filter = new SessionManagementFilter(repo); - filter.setAuthenticatedSessionStrategy(strategy); + filter.setSessionAuthenticationStrategy(strategy); HttpServletRequest request = new MockHttpServletRequest(); authenticateUser(); @@ -94,7 +94,7 @@ public class SessionManagementFilterTests { // repo will return false to containsContext() SessionAuthenticationStrategy strategy = mock(SessionAuthenticationStrategy.class); SessionManagementFilter filter = new SessionManagementFilter(repo); - filter.setAuthenticatedSessionStrategy(strategy); + filter.setSessionAuthenticationStrategy(strategy); MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestedSessionId("xxx"); request.setRequestedSessionIdValid(false);