SEC-1229: Further doc and mods to namespace config/naming to make it more consistent

This commit is contained in:
Luke Taylor 2009-10-03 16:08:51 +00:00
parent dd3b9553a0
commit 2b89ebdfbb
13 changed files with 276 additions and 181 deletions

View File

@ -120,7 +120,7 @@ public class FormLoginBeanDefinitionParser {
}
if (sessionStrategy != null) {
filterBuilder.addPropertyValue("authenticatedSessionStrategy", sessionStrategy);
filterBuilder.addPropertyValue("sessionAuthenticationStrategy", sessionStrategy);
}
if (StringUtils.hasText(failureHandlerRef)) {

View File

@ -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;

View File

@ -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}?

View File

@ -953,6 +953,16 @@
<xs:documentation>The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="session-authentication-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the SessionAuthenticationStrategy instance used by the SessionManagementFilter</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="session-authentication-error-url" type="xs:token">
<xs:annotation>
<xs:documentation>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. </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="concurrency-control.attlist">
@ -968,12 +978,7 @@
</xs:attribute>
<xs:attribute name="error-if-maximum-exceeded" type="security:boolean">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="error-url" type="xs:token">
<xs:annotation>
<xs:documentation>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. </xs:documentation>
<xs:documentation>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.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="session-registry-alias" type="xs:token">

View File

@ -706,22 +706,22 @@ public class HttpSecurityBeanDefinitionParserTests {
public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
setContext(
"<http auto-config='true'>" +
" <session-management>" +
" <concurrency-control max-sessions='2' error-if-maximum-exceeded='true' error-url='/max-exceeded' />" +
" <session-management session-authentication-error-url='/max-exceeded'>" +
" <concurrency-control max-sessions='2' error-if-maximum-exceeded='true' />" +
" </session-management>" +
"</http>" + 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());
}

View File

@ -17,16 +17,15 @@
<para> The <literal>&lt;http&gt;</literal> element encapsulates the security configuration for
the web layer of your application. It creates a <classname>FilterChainProxy</classname> bean
named "springSecurityFilterChain" which maintains the stack of security filters which make up
the web security configuration <footnote>
<para>See the <link xlink:href="#ns-web-xml"> introductory chapter</link> for how to set up
the mapping from your <literal>web.xml</literal></para>
</footnote>. 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 <link xlink:href="#filter-stack">the filter order table</link> 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
the<classname>FilterChainProxy</classname> bean. You can, of course, still do this if you
need full control of the configuration. </para>
the web security configuration <footnote><para>See the <link xlink:href="#ns-web-xml">
introductory chapter</link> for how to set up the mapping from your
<literal>web.xml</literal></para></footnote>. 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 <link xlink:href="#filter-stack"
>the filter order table</link> 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 the<classname>FilterChainProxy</classname> bean. You can, of course, still do
this if you need full control of the configuration. </para>
<para> All filters which require a reference to the
<interfacename>AuthenticationManager</interfacename> will be automatically injected with the
internal instance created by the namespace configuration (see the <link
@ -59,17 +58,6 @@
<para> Whether test URLs should be converted to lower case prior to comparing with defined
path patterns. If unspecified, defaults to "true" </para>
</section>
<section xml:id="session-fixation-protection">
<title><literal>session-fixation-protection</literal></title>
<para> 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". </para>
<para> If enabled this will add a <classname>SessionFixationProtectionFilter</classname> to
the stack. The session fixation protection options on namespace-created instances of
<classname>AbstractAuthenticationProcessingFilter</classname> will also be set
appropriately. </para>
</section>
<section xml:id="nsa-realm">
<title><literal>realm</literal></title>
<para> Sets the realm name used for basic authentication (if enabled). Corresponds to the
@ -124,8 +112,9 @@
default <interfacename>AccessDeniedHandler</interfacename> used by the
<classname>ExceptionTranslationFilter</classname>, (using the
<literal>error-page</literal> attribute, or to supply your own implementation using the
<literal>ref</literal> attribute. This is discussed in more detail in the section on
<link xlink:href="#access-denied-handler">the <classname>ExceptionTranslationFilter</classname></link>.</para>
<literal>ref</literal> attribute. This is discussed in more detail in the section on <link
xlink:href="#access-denied-handler">the
<classname>ExceptionTranslationFilter</classname></link>.</para>
</section>
<section>
<title>The <literal>&lt;intercept-url&gt;</literal> Element</title>
@ -161,7 +150,8 @@
there is no preference. If this attribute is present on any
<literal>&lt;intercept-url&gt;</literal> element, then a
<classname>ChannelProcessingFilter</classname> will be added to the filter stack and its
additional dependencies added to the application context. <!--See the chapter on <link
additional dependencies added to the application context.
<!--See the chapter on <link
xlink:href="#channel-security-config">channel security</link> for an example
configuration using traditional beans. --></para>
<para> If a <literal>&lt;port-mappings&gt;</literal> configuration is added, this will be
@ -185,14 +175,13 @@
the filter stack and an <classname>LoginUrlAuthenticationEntryPoint</classname> 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" <footnote>
<para>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
<classname>DefaultLoginPageGeneratingFilter</classname> is responsible for rendering
the login page and will provide login forms for both normal form login and/or OpenID if
required.</para>
</footnote> The behaviour can be customized using the following attributes. </para>
be generated automatically at the URL "/spring-security-login" <footnote><para>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 <classname>DefaultLoginPageGeneratingFilter</classname> is responsible for
rendering the login page and will provide login forms for both normal form login and/or
OpenID if required.</para></footnote> The behaviour can be customized using the
following attributes. </para>
<section>
<title><literal>login-page</literal></title>
<para> The URL that should be used to render the login page. Maps to the
@ -232,18 +221,18 @@
</section>
<section>
<title><literal>authentication-success-handler-ref</literal></title>
<para>This can be used as an alternative to <literal>default-target-url</literal>
and <literal>always-use-default-target</literal>, giving you full control over the navigation flow
after a successful authentication. The value should be he name of an <interfacename>AuthenticationSuccessHandler</interfacename>
bean in the application context.
</para>
<para>This can be used as an alternative to <literal>default-target-url</literal> and
<literal>always-use-default-target</literal>, giving you full control over the
navigation flow after a successful authentication. The value should be he name of an
<interfacename>AuthenticationSuccessHandler</interfacename> bean in the application
context. </para>
</section>
<section>
<title><literal>authentication-failure-handler-ref</literal></title>
<para>Can be used as an alternative to <literal>authentication-failure-url</literal>, giving you full control over the navigation flow
after an authentication failure. The value should be he name of an <interfacename>AuthenticationFailureHandler</interfacename>
bean in the application context.
</para>
<para>Can be used as an alternative to <literal>authentication-failure-url</literal>, giving
you full control over the navigation flow after an authentication failure. The value
should be he name of an <interfacename>AuthenticationFailureHandler</interfacename> bean
in the application context. </para>
</section>
</section>
<section xml:id="nsa-http-basic">
@ -286,11 +275,9 @@
<title>The <literal>key</literal> Attribute</title>
<para>Maps to the "key" property of <classname>AbstractRememberMeServices</classname>.
Should be set to a unique value to ensure that remember-me cookies are only valid within
the one application <footnote>
<para>This doesn't affect the use of
the one application <footnote><para>This doesn't affect the use of
<classname>PersistentTokenBasedRememberMeServices</classname>, where the tokens are
stored on the server side.</para>
</footnote>. </para>
stored on the server side.</para></footnote>. </para>
</section>
<section>
<title><literal>token-validity-seconds</literal></title>
@ -308,21 +295,33 @@
explicitly using this attribute. </para>
</section>
</section>
<section xml:id="nsa-session-mgmt">
<title>The <literal>&lt;session-management&gt;</literal> Element</title>
<para>Session-management related functionality is implemented by the addition of a
<classname>SessionManagementFilter</classname> to the filter stack.</para>
<section xml:id="session-fixation-protection">
<title><literal>session-fixation-protection</literal></title>
<para> 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".</para>
<para>
If session fixation protection is enabled, the <classname>SessionManagementFilter</classname>
is inected with a appropriately configured <classname>DefaultSessionAuthenticationStrategy</classname>.
See the Javadoc for this class for more details.
</para>
</section>
</section>
<section xml:id="nsa-concurrent-session-control">
<title>The <literal>&lt;concurrent-session-control&gt;</literal> Element</title>
<title>The <literal>&lt;concurrency-control&gt;</literal> Element</title>
<para> Adds support for concurrent session control, allowing limits to be placed on the number
of active sessions a user can have. A <classname>ConcurrentSessionFilter</classname> will be
created, along with a <classname>ConcurrentSessionControllerImpl</classname> and an instance
of <interfacename>SessionRegistry</interfacename> (a
created, and a <classname>ConcurrentSessionControlStrategy</classname> will be used with the
<classname>SessionManagementFilter</classname>. If a <literal>form-login</literal>
element has been declared, the strategy object will also be injected into the created
authentication filter. An instance of <interfacename>SessionRegistry</interfacename> (a
<classname>SessionRegistryImpl</classname> instance unless the user wishes to use a custom
bean). The controller is registered with the namespace's
<interfacename>AuthenticationManager</interfacename>
(<classname>ProviderManager</classname>). Other namespace-created beans which require a
reference to the <interfacename>SessionRegistry</interfacename> will automatically have it
injected. </para>
<para> Note that the <literal>forceEagerSessionCreation</literal> of
<classname>HttpSessionContextIntegrationFilter</classname> will be set to
<literal>true</literal> if concurrent session control is in use. </para>
bean) will be created for use by the strategy.</para>
<section>
<title>The <literal>max-sessions</literal> attribute</title>
<para>Maps to the <literal>maximumSessions</literal> property of
@ -337,10 +336,10 @@
expiry message will just be written directly back to the response. </para>
</section>
<section>
<title>The <literal>exception-if-maximum-exceeded</literal> attribute</title>
<para>If set to "true" a <exceptionname>ConcurrentLoginException</exceptionname> should be
raised when a user attempts to exceed the maximum allowed number of sessions. The default
behaviour is to expire the original session. </para>
<title>The <literal>error-if-maximum-exceeded</literal> attribute</title>
<para>If set to "true" a <exceptionname>SessionAuthenticationException</exceptionname> will
be raised when a user attempts to exceed the maximum allowed number of sessions. The
default behaviour is to expire the original session. </para>
</section>
<section>
<title>The <literal>session-registry-alias</literal> and
@ -438,7 +437,8 @@
<section>
<title>The <literal>&lt;authentication-provider&gt;</literal> Element</title>
<para> This element is basically a shorthand syntax for configuring a <link
xlink:href="#core-services-dao-provider"><classname>DaoAuthenticationProvider</classname></link>.
xlink:href="#core-services-dao-provider"
><classname>DaoAuthenticationProvider</classname></link>.
<classname>DaoAuthenticationProvider</classname> loads user information from a
<interfacename>UserDetailsService</interfacename> and compares the username/password
combination with the values supplied at login. The
@ -447,15 +447,15 @@
<literal>user-service-ref</literal> attribute to point to a bean defined elsewhere in
the application context). You can find examples of these variations in the <link
xlink:href="#ns-auth-providers">namespace introduction</link>. </para>
<section>
<title>The <literal>&lt;password-encoder&gt;</literal> Element</title>
<para>Authentication providers can optionally be configured to use a password encoder as
described in the <link xlink:href="#ns-password-encoder">namespace introduction</link>.
This will result in the bean being injected with the appropriate <interfacename>PasswordEncoder</interfacename>
instance, potentially with an accompanying <interfacename>SaltSource</interfacename> bean to
provide salt values for hashing.
</para>
</section>
<section>
<title>The <literal>&lt;password-encoder&gt;</literal> Element</title>
<para>Authentication providers can optionally be configured to use a password encoder as
described in the <link xlink:href="#ns-password-encoder">namespace introduction</link>.
This will result in the bean being injected with the appropriate
<interfacename>PasswordEncoder</interfacename> instance, potentially with an
accompanying <interfacename>SaltSource</interfacename> bean to provide salt values for
hashing. </para>
</section>
</section>
<section>
<title>Using <literal>&lt;authentication-provider&gt;</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>&lt;protect-pointcut&gt;</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>&lt;protect-pointcut&gt;</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>&lt;after-invocation-provider&gt;</literal> Element</title>

View File

@ -1,15 +1,65 @@
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="concurrent-sessions"
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="session-mgmt"
xmlns:xlink="http://www.w3.org/1999/xlink">
<info>
<title>Session Management</title>
</info>
<!-- TODO: Expand and refer to namespace options -->
<para>HTTP session related functonality is handled by a combination of the
<classname>SessionManagementFilter</classname> and the
<interfacename>SessionAuthenticationStrategy</interfacename> 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.</para>
<section>
<title>SessionManagementFilter</title>
<para>HTTP session related functonality is handled by the
<classname>SessionManagementFilter</classname>. This </para>
<para>The <classname>SessionManagementFilter</classname> checks the contents of the
<interfacename>SecurityContextRepository</interfacename> against the current contents of the
<classname>SecurityContextHolder</classname> 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 <footnote><para>Authentication by mechanisms which perform a redirect
after authenticating (such as form-login) will not be detected by <classname>SessionManagementFilter</classname>,
as the filter will not be invoked during the authenticating request. Session-management functionality has to be
handled separately in these cases.
</para></footnote>.
If the repository contains a security context, the
filter does nothing. If it doesn't, and the thread-local
<interfacename>SecurityContext</interfacename> contains a (non-anonymous)
<interfacename>Authentication</interfacename> object, the filter assumes they have been
authenticated by a previous filter in the stack. It will then invoke the configured
<interfacename>SessionAuthenticationStrategy</interfacename>.</para>
<para>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 <literal>invalidSessionUrl</literal> if set. The easiest way to configure this is
through the namespace, <link xlink:href="#ns-session-mgmt">as described earlier</link>.</para>
</section>
<section>
<title><interfacename>SessionAuthenticationStrategy</interfacename></title>
<para>
<interfacename>SessionAuthenticationStrategy</interfacename> is used by both <classname>SessionManagementFilter</classname>
and <classname>AbstractAutheticationProcessingFilter</classname>, 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
<programlisting><![CDATA[
<http>
<custom-filter position="AUTHENTICATION_PROCESSING_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="myAuthFilter"
class="org.springframework.security.web.authentcation.UsernamePasswordAuthenticationProcessingFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
...
</beans:bean>
<beans:bean id="sas"
class="org.springframework.security.web.session.DefaultAuthenticatedSessionStrategy">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
</beans:bean>
]]>
</programlisting>
</para>
</section>
<section xml:id="concurrent-sessions">
<title>Concurrency Control</title>
<para>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.</para>
<para>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. </para>
<para>The implementation has changed substantially in Spring Security 3. Previously the
concurrent authentication check was made by the <classname>ProviderManager</classname>, which
could be injected with a <literal>ConcurrentSessionController</literal> 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 <interfacename>AuthenticationManager</interfacename>
and once they are successfully authenticated, a session is created and the check is made
whether they are allowed to have another session open.</para>
<para>The implementation uses a specialized version of
<interfacename>SessionAuthenticationStrategy</interfacename>, called
<classname>ConcurrentSessionControlStrategy</classname>.
<note><para>Previously the concurrent authentication check was made by the
<classname>ProviderManager</classname>, which could be injected with a
<literal>ConcurrentSessionController</literal> 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 <interfacename>AuthenticationManager</interfacename> and once
they are successfully authenticated, a session is created and the check is made whether they
are allowed to have another session open.</para></note></para>
<para>To use concurrent session support, you'll need to add the following to
<literal>web.xml</literal>: <programlisting><![CDATA[
<listener>
@ -33,40 +86,50 @@
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener> ]]>
</programlisting></para>
</programlisting></para>
<para>In addition, you will need to add the
<literal>org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter</literal>
to your <classname>FilterChainProxy</classname>. The
<classname>ConcurrentSessionFilter</classname> requires two properties,
<literal>sessionRegistry</literal>, which generally points to an instance of
<literal>SessionRegistryImpl</literal>, and <literal>expiredUrl</literal>, which points to
the page to display when a session has expired.</para>
<para>The <literal>web.xml</literal>
<literal>HttpSessionEventPublisher</literal> causes an <literal>ApplicationEvent</literal> to
be published to the Spring <literal>ApplicationContext</literal> every time a
<literal>HttpSession</literal> commences or terminates. This is critical, as it allows the
<classname>SessionRegistryImpl</classname> to be notified when a session ends.</para>
<para>You will also need to wire up the <classname>ConcurrentSessionControllerImpl</classname>
and refer to it from your <literal>ProviderManager</literal> bean:</para>
<para>
<programlisting><![CDATA[
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<!-- your providers go here -->
</property>
<property name="sessionController" ref="concurrentSessionController"/>
</bean>
<bean id="concurrentSessionController" class=
"org.springframework.security.authentication.concurrent.ConcurrentSessionControllerImpl">
<property name="maximumSessions" value="1"/>
<property name="sessionRegistry">
<bean
class="org.springframework.security.authentication.concurrent.SessionRegistryImpl"/>
<property>
</bean>
]]></programlisting>
the page to display when a session has expired. A configuration using the namespace to create the
<classname>FilterChainProxy</classname> and other default beans might look like this:
<programlisting><![CDATA[
<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="AUTHENTICATION_PROCESSING_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/session-expired.htm" />
</beans:bean>
<beans:bean id="myAuthFilter"
class="org.springframework.security.web.authentcation.UsernamePasswordAuthenticationProcessingFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
...
</beans:bean>
<beans:bean id="sas"
class="org.springframework.security.web.session.ConcurrentSessionControlStrategy">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.authentication.concurrent.SessionRegistryImpl" />
]]>
</programlisting>
</para>
<para>Adding the listener to <filename>web.xml</filename> causes an <literal>ApplicationEvent</literal> to
be published to the Spring <literal>ApplicationContext</literal> every time a
<literal>HttpSession</literal> commences or terminates. This is critical, as it allows the
<classname>SessionRegistryImpl</classname> 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.</para>
</section>
</chapter>

View File

@ -355,19 +355,31 @@
in <xref xlink:href="#channel-security"/-->
</para>
</section>
<section xml:id="ns-concurrent-session">
<section xml:id="ns-session-mgmt">
<title>Session Management</title>
<para> 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 <filename>web.xml</filename>
file to keep Spring Security updated about session lifecycle events: <programlisting language="xml">
<![CDATA[
<section>
<title>Detecting Timeouts</title>
<para> 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
<literal>session-management</literal> element:<![CDATA[
<http>
...
<session-management invalid-session-url="/sessionTimeout.htm" />
</http>]]></para>
</section>
<section xml:id="ns-concurrent-sessions">
<title>Concurrent Session Control</title>
<para>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
<filename>web.xml</filename> file to keep Spring Security updated about session
lifecycle events: <![CDATA[
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
]]></programlisting> Then add the following lines to your application context: <programlisting language="xml"><![CDATA[
]]> Then add the following lines to your application context: <programlisting language="xml"><![CDATA[
<http>
...
<session-management>
@ -375,8 +387,8 @@
</session-management>
</http>]]>
</programlisting> 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 <programlisting language="xml"><![CDATA[
second login will cause the first to be invalidated. Often you would prefer to prevent a
second login, in which case you can use <programlisting language="xml"><![CDATA[
<http>
...
<session-management>
@ -384,13 +396,17 @@
</session-management>
</http>]]>
</programlisting> The second login will then be rejected. By
<quote>rejected</quote>, we mean that the user will be sent to the
<literal>authentication-failure-url</literal> if form-based login is being used. If the
second authentication takes place through another non-interactive mechanism, such as
<quote>remember-me</quote>, an <quote>unauthorized</quote> (402) error will be sent to the
client. If instead you want to use an error page, you can add the attribute
<literal>error-url</literal> to the <literal>concurrency-control</literal>
element.<!-- TODO: Link to main section in docs --></para>
<quote>rejected</quote>, we mean that the user will be sent to the
<literal>authentication-failure-url</literal> if form-based login is being used. If the
second authentication takes place through another non-interactive mechanism, such as
<quote>remember-me</quote>, an <quote>unauthorized</quote> (402) error will be sent to
the client. If instead you want to use an error page, you can add the attribute
<literal>session-authentication-error-url</literal> to the
<literal>session-management</literal> element. </para>
<para> 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 <link xlink:href="#session-mgmt">Session Management chapter</link>. </para>
</section>
</section>
<section xml:id="ns-openid">
<title>OpenID Login</title>

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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));

View File

@ -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);