Skip to content

Commit

Permalink
Extract FilterSecurityInterceptor Docs
Browse files Browse the repository at this point in the history
Closes gh-8001
  • Loading branch information
rwinch committed Feb 21, 2020
1 parent b8d4f33 commit 32ee30e
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,6 @@
There are some key filters which will always be used in a web application which uses Spring Security, so we'll look at these and their supporting classes and interfaces first.
We won't cover every feature, so be sure to look at the Javadoc for them if you want to get the complete picture.

[[filter-security-interceptor]]
=== FilterSecurityInterceptor
We've already seen `FilterSecurityInterceptor` briefly when discussing <<tech-intro-access-control,access-control in general>>, and we've already used it with the namespace where the `<intercept-url>` elements are combined to configure it internally.
Now we'll see how to explicitly configure it for use with a `FilterChainProxy`, along with its companion filter `ExceptionTranslationFilter`.
A typical configuration example is shown below:

[source,xml]
----
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source>
<security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-security-metadata-source>
</property>
</bean>
----

`FilterSecurityInterceptor` is responsible for handling the security of HTTP resources.
It requires a reference to an `AuthenticationManager` and an `AccessDecisionManager`.
It is also supplied with configuration attributes that apply to different HTTP URL requests.
Refer back to <<tech-intro-config-attributes,the original discussion on these>> in the technical introduction.

The `FilterSecurityInterceptor` can be configured with configuration attributes in two ways.
The first, which is shown above, is using the `<filter-security-metadata-source>` namespace element.
This is similar to the `<http>` element from the namespace chapter but the `<intercept-url>` child elements only use the `pattern` and `access` attributes.
Commas are used to delimit the different configuration attributes that apply to each HTTP URL.
The second option is to write your own `SecurityMetadataSource`, but this is beyond the scope of this document.
Irrespective of the approach used, the `SecurityMetadataSource` is responsible for returning a `List<ConfigAttribute>` containing all of the configuration attributes associated with a single secure HTTP URL.

It should be noted that the `FilterSecurityInterceptor.setSecurityMetadataSource()` method actually expects an instance of `FilterInvocationSecurityMetadataSource`.
This is a marker interface which subclasses `SecurityMetadataSource`.
It simply denotes the `SecurityMetadataSource` understands `FilterInvocation` s.
In the interests of simplicity we'll continue to refer to the `FilterInvocationSecurityMetadataSource` as a `SecurityMetadataSource`, as the distinction is of little relevance to most users.

The `SecurityMetadataSource` created by the namespace syntax obtains the configuration attributes for a particular `FilterInvocation` by matching the request URL against the configured `pattern` attributes.
This behaves in the same way as it does for namespace configuration.
The default is to treat all expressions as Apache Ant paths and regular expressions are also supported for more complex cases.
The `request-matcher` attribute is used to specify the type of pattern being used.
It is not possible to mix expression syntaxes within the same definition.
As an example, the previous configuration using regular expressions instead of Ant paths would be written as follows:

[source,xml]
----
<bean id="filterInvocationInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="runAsManager" ref="runAsManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source request-matcher="regex">
<security:intercept-url pattern="\A/secure/super/.*\Z" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="\A/secure/.*\" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-security-metadata-source>
</property>
</bean>
----

Patterns are always evaluated in the order they are defined.
Thus it is important that more specific patterns are defined higher in the list than less specific patterns.
This is reflected in our example above, where the more specific `/secure/super/` pattern appears higher than the less specific `/secure/` pattern.
If they were reversed, the `/secure/` pattern would always match and the `/secure/super/` pattern would never be evaluated.

[[exception-translation-filter]]
=== ExceptionTranslationFilter
The `ExceptionTranslationFilter` sits above the `FilterSecurityInterceptor` in the security filter stack.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Below is a comprehensive list of Spring Security Filter ordering:
* OAuth2AuthorizationCodeGrantFilter
* SessionManagementFilter
* ExceptionTranslationFilter
* FilterSecurityInterceptor
* <<servlet-authorization-filtersecurityinterceptor,`FilterSecurityInterceptor`>>
* SwitchUserFilter

include::technical-overview.adoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// from the original documentation

[[authz-arch]]
== Authorization Architecture
= Authorization Architecture


[[authz-authorities]]
=== Authorities
== Authorities
As we saw in the <<tech-granted-authority,technical overview>>, all `Authentication` implementations store a list of `GrantedAuthority` objects.
These represent the authorities that have been granted to the principal.
the `GrantedAuthority` objects are inserted into the `Authentication` object by the `AuthenticationManager` and are later read by `AccessDecisionManager` s when making authorization decisions.
Expand All @@ -19,7 +20,7 @@ String getAuthority();
----

This method allows
`AccessDecisionManager` s to obtain a precise `String` representation of the `GrantedAuthority`.
`AccessDecisionManager` s to obtain a precise `String` representation of the `GrantedAuthority`.
By returning a representation as a `String`, a `GrantedAuthority` can be easily "read" by most `AccessDecisionManager` s.
If a `GrantedAuthority` cannot be precisely represented as a `String`, the `GrantedAuthority` is considered "complex" and `getAuthority()` must return `null`.

Expand All @@ -33,13 +34,13 @@ All `AuthenticationProvider` s included with the security architecture use `Simp


[[authz-pre-invocation]]
=== Pre-Invocation Handling
== Pre-Invocation Handling
As we've also seen in the <<secure-objects,Technical Overview>> chapter, Spring Security provides interceptors which control access to secure objects such as method invocations or web requests.
A pre-invocation decision on whether the invocation is allowed to proceed is made by the `AccessDecisionManager`.


[[authz-access-decision-manager]]
==== The AccessDecisionManager
=== The AccessDecisionManager
The `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` and is responsible for making final access control decisions.
the `AccessDecisionManager` interface contains three methods:

Expand All @@ -63,7 +64,7 @@ The `supports(ConfigAttribute)` method is called by the `AbstractSecurityInterce
The `supports(Class)` method is called by a security interceptor implementation to ensure the configured `AccessDecisionManager` supports the type of secure object that the security interceptor will present.

[[authz-voting-based]]
==== Voting-Based AccessDecisionManager Implementations
=== Voting-Based AccessDecisionManager Implementations
Whilst users can implement their own `AccessDecisionManager` to control all aspects of authorization, Spring Security includes several `AccessDecisionManager` implementations that are based on voting.
<<authz-access-voting>> illustrates the relevant classes.

Expand Down Expand Up @@ -105,7 +106,7 @@ For example, votes from a particular `AccessDecisionVoter` might receive additio


[[authz-role-voter]]
===== RoleVoter
==== RoleVoter
The most commonly used `AccessDecisionVoter` provided with Spring Security is the simple `RoleVoter`, which treats configuration attributes as simple role names and votes to grant access if the user has been assigned that role.

It will vote if any `ConfigAttribute` begins with the prefix `ROLE_`.
Expand All @@ -115,7 +116,7 @@ If no `ConfigAttribute` begins with `ROLE_`, the voter will abstain.


[[authz-authenticated-voter]]
===== AuthenticatedVoter
==== AuthenticatedVoter
Another voter which we've implicitly seen is the `AuthenticatedVoter`, which can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.

Expand All @@ -124,14 +125,14 @@ See the Javadoc for this class for more information.


[[authz-custom-voter]]
===== Custom Voters
==== Custom Voters
Obviously, you can also implement a custom `AccessDecisionVoter` and you can put just about any access-control logic you want in it.
It might be specific to your application (business-logic related) or it might implement some security administration logic.
For example, you'll find a https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time[blog article] on the Spring web site which describes how to use a voter to deny access in real-time to users whose accounts have been suspended.


[[authz-after-invocation-handling]]
=== After Invocation Handling
== After Invocation Handling
Whilst the `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` before proceeding with the secure object invocation, some applications need a way of modifying the object actually returned by the secure object invocation.
Whilst you could easily implement your own AOP concern to achieve this, Spring Security provides a convenient hook that has several concrete implementations that integrate with its ACL capabilities.

Expand All @@ -153,7 +154,7 @@ This latter (recommended) approach is usually achieved through a `ROLE_USER` or


[[authz-hierarchical-roles]]
=== Hierarchical Roles
== Hierarchical Roles
It is a common requirement that a particular role in an application should automatically "include" other roles.
For example, in an application which has the concept of an "admin" and a "user" role, you may want an admin to be able to do everything a normal user can.
To achieve this, you can either make sure that all admin users are also assigned the "user" role.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,96 @@
[[jc-authorize-requests]]
== Authorize Requests
Our examples have only required users to be authenticated and have done so for every URL in our application.
We can specify custom requirements for our URLs by adding multiple children to our `http.authorizeRequests()` method.
For example:
[[servlet-authorization-filtersecurityinterceptor]]
= Authorize HttpServletRequest with FilterSecurityInterceptor
:figures: images/servlet/authorization
:icondir: images/icons

This section builds on <<servlet-architecture,Servlet Architecture and Implementation>> by digging deeper into how <<authorization>> works within Servlet based applications.

[source,java]
The {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`] provides <<authorization>> for ``HttpServletRequest``s.
It is inserted into the <<servlet-filterchainproxy>> as one of the <<servlet-security-filters>>.

.Authorize HttpServletRequest
image::{figures}/filtersecurityinterceptor.png[]

* image:{icondir}/number_1.png[] First, the `FilterSecurityInterceptor` obtains an <<servlet-authentication-authentication>> from the <<servlet-authentication-securitycontextholder>>.
* image:{icondir}/number_2.png[] Second, `FilterSecurityInterceptor` creates a {security-api-url}org/springframework/security/web/FilterInvocation.html[`FilterInvocation`] from the `HttpServletRequest`, `HttpServletResponse`, and `FilterChain` that are passed into the `FilterSecurityInterceptor`.
// FIXME: link to FilterInvocation
* image:{icondir}/number_3.png[] Next, it passes the `FilterInvocation` to `SecurityMetadataSource` to get the ``ConfigAttribute``s.
* image:{icondir}/number_4.png[] Finally, it passes the `Authentication`, `FilterInvocation`, and ``ConfigAttribute``s to the `AccessDecisionManager`.
** image:{icondir}/number_5.png[] If authorization is denied, an `AccessDeniedException` is thrown.
In this case the <<servlet-exceptiontranslationfilter,`ExceptionTranslationFilter`>> handles the `AccessDeniedException`.
** image:{icondir}/number_6.png[] If access is granted, `FilterSecurityInterceptor` continues with the <<servlet-filters-review,FilterChain>> which allows the application to process normally.

// configuration (xml/java)

By default, Spring Security's authorization will require all requests to be authenticated.
The explicit configuration looks like:

.Every Request Must be Authenticated
====
.Java
[source,java,role="primary"]
----
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
}
----
.XML
[source,xml,role="secondary"]
----
<http>
<!-- ... -->
<intercept-url pattern="/**" access="authenticated"/>
</http>
----
====

We can configure Spring Security to have different rules by adding more rules in order of precedence.

.Authorize Requests
====
.Java
[source,java,role="primary"]
----
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize // <1>
.antMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.antMatchers("/admin/**").hasRole("ADMIN") // <3>
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4>
.anyRequest().authenticated() // <5>
)
.formLogin(withDefaults());
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.mvcMatchers("/admin/**").hasRole("ADMIN") // <3>
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4>
.anyRequest().denyAll() // <5>
);
}
----
<1> There are multiple children to the `http.authorizeRequests()` method each matcher is considered in the order they were declared.
.XML
[source,xml,role="secondary"]
----
<http> <!--1-->
<!-- ... -->
<!--2-->
<intercept-url pattern="/resources/**" access="permitAll"/>
<intercept-url pattern="/signup" access="permitAll"/>
<intercept-url pattern="/about" access="permitAll"/>
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> <!--3-->
<intercept-url pattern="/db/**" access="hasRole('ADMIN') and hasRole('DBA')"/> <!--4-->
<intercept-url pattern="/**" access="denyAll"/> <!--5-->
</http>
----
====
<1> There are multiple authorization rules specified.
Each rule is considered in the order they were declared.
<2> We specified multiple URL patterns that any user can access.
Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
<3> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
<4> Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
You will notice that since we are using the `hasRole` expression we do not need to specify the "ROLE_" prefix.
<5> Any URL that has not already been matched on only requires that the user be authenticated
<5> Any URL that has not already been matched on is denied access.
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ Irrespective of how you choose to authenticate - whether using a Spring Security
In this part we'll explore the different `AbstractSecurityInterceptor` implementations, which were introduced in Part I.
We then move on to explore how to fine-tune authorization through use of domain access control lists.

include::architecture.adoc[]
include::architecture.adoc[leveloffset=+1]

include::secure-objects.adoc[]
include::authorize-requests.adoc[leveloffset=+1]

include::expression-based.adoc[]

include::authorize-requests.adoc[]
include::secure-objects.adoc[]

include::method-security.adoc[]

Expand Down

0 comments on commit 32ee30e

Please sign in to comment.