15 changed files with 1794 additions and 1055 deletions
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
File diff suppressed because it is too large
Load Diff
@ -1,183 +0,0 @@
@@ -1,183 +0,0 @@
|
||||
[[servlet-authorization-filtersecurityinterceptor]] |
||||
= Authorize HttpServletRequest with FilterSecurityInterceptor |
||||
:figures: servlet/authorization |
||||
|
||||
[NOTE] |
||||
==== |
||||
`FilterSecurityInterceptor` is in the process of being replaced by xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`]. |
||||
Consider using that instead. |
||||
==== |
||||
|
||||
This section builds on xref:servlet/architecture.adoc#servlet-architecture[Servlet Architecture and Implementation] by digging deeper into how xref:servlet/authorization/index.adoc#servlet-authorization[authorization] works within Servlet-based applications. |
||||
|
||||
The {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`] provides xref:servlet/authorization/index.adoc#servlet-authorization[authorization] for `HttpServletRequest` instances. |
||||
It is inserted into the xref:servlet/architecture.adoc#servlet-filterchainproxy[FilterChainProxy] as one of the xref:servlet/architecture.adoc#servlet-security-filters[Security Filters]. |
||||
|
||||
The following image shows the role of `FilterSecurityInterceptor`: |
||||
|
||||
.Authorize HttpServletRequest |
||||
image::{figures}/filtersecurityinterceptor.png[] |
||||
|
||||
image:{icondir}/number_1.png[] The `FilterSecurityInterceptor` obtains an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] from the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder]. |
||||
image:{icondir}/number_2.png[] `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`. |
||||
image:{icondir}/number_3.png[] It passes the `FilterInvocation` to `SecurityMetadataSource` to get the ``ConfigAttribute``s. |
||||
image:{icondir}/number_4.png[] 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 xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] handles the `AccessDeniedException`. |
||||
image:{icondir}/number_6.png[] If access is granted, `FilterSecurityInterceptor` continues with the xref:servlet/architecture.adoc#servlet-filters-review[`FilterChain`], which lets the application process normally. |
||||
|
||||
// configuration (xml/java) |
||||
|
||||
By default, Spring Security's authorization requires all requests to be authenticated. |
||||
The following listing shows the explicit configuration: |
||||
|
||||
[[servlet-authorize-requests-defaults]] |
||||
.Every Request Must be Authenticated |
||||
==== |
||||
.Java |
||||
[source,java,role="primary"] |
||||
---- |
||||
@Bean |
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||
http |
||||
// ... |
||||
.authorizeRequests(authorize -> authorize |
||||
.anyRequest().authenticated() |
||||
); |
||||
return http.build(); |
||||
} |
||||
---- |
||||
|
||||
.XML |
||||
[source,xml,role="secondary"] |
||||
---- |
||||
<http> |
||||
<!-- ... --> |
||||
<intercept-url pattern="/**" access="authenticated"/> |
||||
</http> |
||||
---- |
||||
|
||||
.Kotlin |
||||
[source,kotlin,role="secondary"] |
||||
---- |
||||
@Bean |
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain { |
||||
http { |
||||
// ... |
||||
authorizeRequests { |
||||
authorize(anyRequest, authenticated) |
||||
} |
||||
} |
||||
return http.build() |
||||
} |
||||
---- |
||||
==== |
||||
|
||||
We can configure Spring Security to have different rules by adding more rules in order of precedence: |
||||
|
||||
.Authorize Requests |
||||
==== |
||||
.Java |
||||
[source,java,role="primary"] |
||||
---- |
||||
@Bean |
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||
http |
||||
// ... |
||||
.authorizeRequests(authorize -> authorize // <1> |
||||
.requestMatchers("/resources/**", "/signup", "/about").permitAll() // <2> |
||||
.requestMatchers("/admin/**").hasRole("ADMIN") // <3> |
||||
.requestMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4> |
||||
.anyRequest().denyAll() // <5> |
||||
); |
||||
return http.build(); |
||||
} |
||||
---- |
||||
|
||||
.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> |
||||
---- |
||||
|
||||
.Kotlin |
||||
[source,kotlin,role="secondary"] |
||||
---- |
||||
@Bean |
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain { |
||||
http { |
||||
authorizeRequests { // <1> |
||||
authorize("/resources/**", permitAll) // <2> |
||||
authorize("/signup", permitAll) |
||||
authorize("/about", permitAll) |
||||
|
||||
authorize("/admin/**", hasRole("ADMIN")) // <3> |
||||
authorize("/db/**", "hasRole('ADMIN') and hasRole('DBA')") // <4> |
||||
authorize(anyRequest, denyAll) // <5> |
||||
} |
||||
} |
||||
return http.build() |
||||
} |
||||
---- |
||||
==== |
||||
<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 is denied access. |
||||
This is a good strategy if you do not want to accidentally forget to update your authorization rules. |
||||
==== |
||||
|
||||
|
||||
[[filtersecurityinterceptor-every-request]] |
||||
== Configure FilterSecurityInterceptor with Dispatcher Types |
||||
|
||||
By default, the `FilterSecurityInterceptor` applies to every request. |
||||
This means that if a request is dispatched from a request that was already filtered, the `FilterSecurityInterceptor` will perform the same authorization checks on the dispatched request. |
||||
In some scenarios, you may not want to apply authorization on some dispatcher types: |
||||
|
||||
.Permit ASYNC and ERROR dispatcher types |
||||
==== |
||||
.Java |
||||
[source,java,role="primary"] |
||||
---- |
||||
@Bean |
||||
SecurityFilterChain web(HttpSecurity http) throws Exception { |
||||
http |
||||
.authorizeRequests((authorize) -> authorize |
||||
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR).permitAll() |
||||
.anyRequest.authenticated() |
||||
) |
||||
// ... |
||||
|
||||
return http.build(); |
||||
} |
||||
---- |
||||
.XML |
||||
[source,xml] |
||||
---- |
||||
<http auto-config="true"> |
||||
<intercept-url request-matcher-ref="dispatcherTypeMatcher" access="permitAll" /> |
||||
<intercept-url pattern="/**" access="authenticated"/> |
||||
</http> |
||||
|
||||
<b:bean id="dispatcherTypeMatcher" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher"> |
||||
<b:constructor-arg value="ASYNC"/> |
||||
<b:constructor-arg value="ERROR"/> |
||||
</b:bean> |
||||
---- |
||||
==== |
||||
File diff suppressed because it is too large
Load Diff
@ -1,143 +0,0 @@
@@ -1,143 +0,0 @@
|
||||
|
||||
[[secure-object-impls]] |
||||
= Secure Object Implementations |
||||
|
||||
This section covers how Spring Security handles Secure Object implementations. |
||||
|
||||
[[aop-alliance]] |
||||
== AOP Alliance (MethodInvocation) Security Interceptor |
||||
Prior to Spring Security 2.0, securing `MethodInvocation` instances needed a lot of boiler plate configuration. |
||||
Now the recommended approach for method security is to use xref:servlet/configuration/xml-namespace.adoc#ns-method-security[namespace configuration]. |
||||
This way, the method security infrastructure beans are configured automatically for you, so you need not know about the implementation classes. |
||||
We provide only a quick overview of the classes that are involved here. |
||||
|
||||
Method security is enforced by using a `MethodSecurityInterceptor`, which secures `MethodInvocation` instances. |
||||
Depending on the configuration approach, an interceptor may be specific to a single bean or shared between multiple beans. |
||||
The interceptor uses a `MethodSecurityMetadataSource` instance to obtain the configuration attributes that apply to a particular method invocation. |
||||
`MapBasedMethodSecurityMetadataSource` is used to store configuration attributes keyed by method names (which can be wildcarded) and will be used internally when the attributes are defined in the application context using the `<intercept-methods>` or `<protect-point>` elements. |
||||
Other implementations are used to handle annotation-based configuration. |
||||
|
||||
=== Explicit MethodSecurityInterceptor Configuration |
||||
You can configure a `MethodSecurityInterceptor` directly in your application context for use with one of Spring AOP's proxying mechanisms: |
||||
|
||||
==== |
||||
[source,xml] |
||||
---- |
||||
<bean id="bankManagerSecurity" class= |
||||
"org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor"> |
||||
<property name="authenticationManager" ref="authenticationManager"/> |
||||
<property name="accessDecisionManager" ref="accessDecisionManager"/> |
||||
<property name="afterInvocationManager" ref="afterInvocationManager"/> |
||||
<property name="securityMetadataSource"> |
||||
<sec:method-security-metadata-source> |
||||
<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/> |
||||
<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/> |
||||
</sec:method-security-metadata-source> |
||||
</property> |
||||
</bean> |
||||
---- |
||||
==== |
||||
|
||||
[[aspectj]] |
||||
== AspectJ (JoinPoint) Security Interceptor |
||||
The AspectJ security interceptor is very similar to the AOP Alliance security interceptor discussed in the previous section. |
||||
We discuss only the differences in this section. |
||||
|
||||
The AspectJ interceptor is named `AspectJSecurityInterceptor`. |
||||
Unlike the AOP Alliance security interceptor, which relies on the Spring application context to weave in the security interceptor through proxying, the `AspectJSecurityInterceptor` is woven in through the AspectJ compiler. |
||||
It would not be uncommon to use both types of security interceptors in the same application, with `AspectJSecurityInterceptor` being used for domain object instance security and the AOP Alliance `MethodSecurityInterceptor` being used for services layer security. |
||||
|
||||
We first consider how the `AspectJSecurityInterceptor` is configured in the Spring application context: |
||||
|
||||
==== |
||||
[source,xml] |
||||
---- |
||||
<bean id="bankManagerSecurity" class= |
||||
"org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor"> |
||||
<property name="authenticationManager" ref="authenticationManager"/> |
||||
<property name="accessDecisionManager" ref="accessDecisionManager"/> |
||||
<property name="afterInvocationManager" ref="afterInvocationManager"/> |
||||
<property name="securityMetadataSource"> |
||||
<sec:method-security-metadata-source> |
||||
<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/> |
||||
<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/> |
||||
</sec:method-security-metadata-source> |
||||
</property> |
||||
</bean> |
||||
---- |
||||
==== |
||||
|
||||
The two interceptors can share the same `securityMetadataSource`, as the `SecurityMetadataSource` works with `java.lang.reflect.Method` instances rather than an AOP library-specific class. |
||||
Your access decisions have access to the relevant AOP library-specific invocation (`MethodInvocation` or `JoinPoint`) and can consider a range of additional criteria (such as method arguments) when making access decisions. |
||||
|
||||
Next, you need to define an AspectJ `aspect`, as the following example shows: |
||||
|
||||
==== |
||||
[source,java] |
||||
---- |
||||
|
||||
package org.springframework.security.samples.aspectj; |
||||
|
||||
import org.springframework.security.access.intercept.aspectj.AspectJSecurityInterceptor; |
||||
import org.springframework.security.access.intercept.aspectj.AspectJCallback; |
||||
import org.springframework.beans.factory.InitializingBean; |
||||
|
||||
public aspect DomainObjectInstanceSecurityAspect implements InitializingBean { |
||||
|
||||
private AspectJSecurityInterceptor securityInterceptor; |
||||
|
||||
pointcut domainObjectInstanceExecution(): target(PersistableEntity) |
||||
&& execution(public * *(..)) && !within(DomainObjectInstanceSecurityAspect); |
||||
|
||||
Object around(): domainObjectInstanceExecution() { |
||||
if (this.securityInterceptor == null) { |
||||
return proceed(); |
||||
} |
||||
|
||||
AspectJCallback callback = new AspectJCallback() { |
||||
public Object proceedWithObject() { |
||||
return proceed(); |
||||
} |
||||
}; |
||||
|
||||
return this.securityInterceptor.invoke(thisJoinPoint, callback); |
||||
} |
||||
|
||||
public AspectJSecurityInterceptor getSecurityInterceptor() { |
||||
return securityInterceptor; |
||||
} |
||||
|
||||
public void setSecurityInterceptor(AspectJSecurityInterceptor securityInterceptor) { |
||||
this.securityInterceptor = securityInterceptor; |
||||
} |
||||
|
||||
public void afterPropertiesSet() throws Exception { |
||||
if (this.securityInterceptor == null) |
||||
throw new IllegalArgumentException("securityInterceptor required"); |
||||
} |
||||
} |
||||
} |
||||
---- |
||||
==== |
||||
|
||||
|
||||
In the preceding example, the security interceptor is applied to every instance of `PersistableEntity`, which is an abstract class not shown (you can use any other class or `pointcut` expression you like). |
||||
For those curious, `AspectJCallback` is needed because the `proceed();` statement has special meaning only within an `around()` body. |
||||
The `AspectJSecurityInterceptor` calls this anonymous `AspectJCallback` class when it wants the target object to continue. |
||||
|
||||
You need to configure Spring to load the aspect and wire it with the `AspectJSecurityInterceptor`. |
||||
The following example shows a bean declaration that achieves this: |
||||
|
||||
==== |
||||
[source,xml] |
||||
---- |
||||
|
||||
<bean id="domainObjectInstanceSecurityAspect" |
||||
class="security.samples.aspectj.DomainObjectInstanceSecurityAspect" |
||||
factory-method="aspectOf"> |
||||
<property name="securityInterceptor" ref="bankManagerSecurity"/> |
||||
</bean> |
||||
---- |
||||
==== |
||||
|
||||
Now you can create your beans from anywhere within your application, using whatever means you think fit (e.g. `new Person();`), and they have the security interceptor applied. |
||||
Loading…
Reference in new issue