Skip to content

Commit 070dce1

Browse files
committed
Document ReactiveMethodSecurity improvements
Issue gh-9401
1 parent e990174 commit 070dce1

File tree

1 file changed

+276
-2
lines changed
  • docs/modules/ROOT/pages/reactive/authorization

1 file changed

+276
-2
lines changed

docs/modules/ROOT/pages/reactive/authorization/method.adoc

Lines changed: 276 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,285 @@ For this to work the return type of the method must be a `org.reactivestreams.Pu
1010
This is necessary to integrate with Reactor's `Context`.
1111
====
1212

13+
[[jc-enable-reactive-method-security-authorization-manager]]
14+
== EnableReactiveMethodSecurity with AuthorizationManager
15+
16+
In Spring Security 5.8, we can enable annotation-based security using the `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` annotation on any `@Configuration` instance.
17+
18+
This improves upon `@EnableReactiveMethodSecurity` in a number of ways. `@EnableReactiveMethodSecurity(useAuthorizationManager=true)`:
19+
20+
1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
21+
This simplifies reuse and customization.
22+
2. Supports reactive return types. Note that we are waiting on https://github.com/spring-projects/spring-framework/issues/22462[additional coroutine support from the Spring Framework] before adding coroutine support.
23+
3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
24+
4. Checks for conflicting annotations to ensure an unambiguous security configuration
25+
5. Complies with JSR-250
26+
27+
[NOTE]
28+
====
29+
For earlier versions, please read about similar support with <<jc-enable-reactive-method-security, @EnableReactiveMethodSecurity>>.
30+
====
31+
32+
For example, the following would enable Spring Security's `@PreAuthorize` annotation:
33+
34+
.Method Security Configuration
35+
====
36+
.Java
37+
[source,java,role="primary"]
38+
----
39+
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
40+
public class MethodSecurityConfig {
41+
// ...
42+
}
43+
----
44+
====
45+
46+
Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly.
47+
Spring Security's native annotation support defines a set of attributes for the method.
48+
These will be passed to the various method interceptors, like `AuthorizationManagerBeforeReactiveMethodInterceptor`, for it to make the actual decision:
49+
50+
.Method Security Annotation Usage
51+
====
52+
.Java
53+
[source,java,role="primary"]
54+
----
55+
public interface BankService {
56+
@PreAuthorize("hasRole('USER')")
57+
Mono<Account> readAccount(Long id);
58+
59+
@PreAuthorize("hasRole('USER')")
60+
Flux<Account> findAccounts();
61+
62+
@PreAuthorize("@func.apply(#account)")
63+
Mono<Account> post(Account account, Double amount);
64+
}
65+
----
66+
====
67+
68+
In this case `hasRole` refers to the method found in `SecurityExpressionRoot` that gets invoked by the SpEL evaluation engine.
69+
70+
`@bean` refers to a custom component you have defined, where `apply` can return `Boolean` or `Mono<Boolean>` to indicate the authorization decision.
71+
A bean like that might look something like this:
72+
73+
.Method Security Reactive Boolean Expression
74+
====
75+
.Java
76+
[source,java,role="primary"]
77+
----
78+
@Bean
79+
public Function<Account, Mono<Boolean>> func() {
80+
return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
81+
}
82+
----
83+
====
84+
85+
=== Customizing Authorization
86+
87+
Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support.
88+
89+
[[jc-reactive-method-security-custom-granted-authority-defaults]]
90+
91+
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
92+
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
93+
94+
.Custom MethodSecurityExpressionHandler
95+
====
96+
.Java
97+
[source,java,role="primary"]
98+
----
99+
@Bean
100+
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
101+
return new GrantedAuthorityDefaults("MYPREFIX_");
102+
}
103+
----
104+
====
105+
106+
[TIP]
107+
====
108+
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
109+
====
110+
111+
[[jc-reactive-method-security-custom-authorization-manager]]
112+
=== Custom Authorization Managers
113+
114+
Method authorization is a combination of before- and after-method authorization.
115+
116+
[NOTE]
117+
====
118+
Before-method authorization is performed before the method is invoked.
119+
If that authorization denies access, the method is not invoked, and an `AccessDeniedException` is thrown.
120+
After-method authorization is performed after the method is invoked, but before the method returns to the caller.
121+
If that authorization denies access, the value is not returned, and an `AccessDeniedException` is thrown
122+
====
123+
124+
To recreate what adding `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` does by default, you would publish the following configuration:
125+
126+
.Full Pre-post Method Security Configuration
127+
====
128+
.Java
129+
[source,java,role="primary"]
130+
----
131+
@Configuration
132+
class MethodSecurityConfig {
133+
@Bean
134+
BeanDefinitionRegistryPostProcessor aopConfig() {
135+
return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
136+
}
137+
138+
@Bean
139+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
140+
PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor() {
141+
return new PreFilterAuthorizationReactiveMethodInterceptor();
142+
}
143+
144+
@Bean
145+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
146+
AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor() {
147+
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize();
148+
}
149+
150+
@Bean
151+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
152+
AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor() {
153+
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize();
154+
}
155+
156+
@Bean
157+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
158+
PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor() {
159+
return new PostFilterAuthorizationReactiveMethodInterceptor();
160+
}
161+
}
162+
----
163+
====
164+
165+
Notice that Spring Security's method security is built using Spring AOP.
166+
So, interceptors are invoked based on the order specified.
167+
This can be customized by calling `setOrder` on the interceptor instances like so:
168+
169+
.Publish Custom Advisor
170+
====
171+
.Java
172+
[source,java,role="primary"]
173+
----
174+
@Bean
175+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
176+
Advisor postFilterAuthorizationMethodInterceptor() {
177+
PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
178+
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
179+
return interceptor;
180+
}
181+
----
182+
====
183+
184+
You may want to only support `@PreAuthorize` in your application, in which case you can do the following:
185+
186+
.Only @PreAuthorize Configuration
187+
====
188+
.Java
189+
[source,java,role="primary"]
190+
----
191+
@Configuration
192+
class MethodSecurityConfig {
193+
@Bean
194+
BeanDefinitionRegistryPostProcessor aopConfig() {
195+
return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
196+
}
197+
198+
@Bean
199+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
200+
Advisor preAuthorize() {
201+
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
202+
}
203+
}
204+
----
205+
====
206+
207+
Or, you may have a custom before-method `ReactiveAuthorizationManager` that you want to add to the list.
208+
209+
In this case, you will need to tell Spring Security both the `ReactiveAuthorizationManager` and to which methods and classes your authorization manager applies.
210+
211+
Thus, you can configure Spring Security to invoke your `ReactiveAuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so:
212+
213+
.Custom Before Advisor
214+
====
215+
216+
.Java
217+
[source,java,role="primary"]
218+
----
219+
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
220+
class MethodSecurityConfig {
221+
@Bean
222+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
223+
public Advisor customAuthorize() {
224+
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
225+
pattern.setPattern("org.mycompany.myapp.service.*");
226+
ReactiveAuthorizationManager<MethodInvocation> rule = AuthorityAuthorizationManager.isAuthenticated();
227+
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(pattern, rule);
228+
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
229+
return interceptor;
230+
}
231+
}
232+
----
233+
====
234+
235+
[TIP]
236+
====
237+
You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
238+
====
239+
240+
The same can be done for after-method authorization.
241+
After-method authorization is generally concerned with analysing the return value to verify access.
242+
243+
For example, you might have a method that confirms that the account requested actually belongs to the logged-in user like so:
244+
245+
.@PostAuthorize example
246+
====
247+
.Java
248+
[source,java,role="primary"]
249+
----
250+
public interface BankService {
251+
252+
@PreAuthorize("hasRole('USER')")
253+
@PostAuthorize("returnObject.owner == authentication.name")
254+
Mono<Account> readAccount(Long id);
255+
}
256+
----
257+
====
258+
259+
You can supply your own `AuthorizationMethodInterceptor` to customize how access to the return value is evaluated.
260+
261+
For example, if you have your own custom annotation, you can configure it like so:
262+
263+
264+
.Custom After Advisor
265+
====
266+
.Java
267+
[source,java,role="primary"]
268+
----
269+
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
270+
class MethodSecurityConfig {
271+
@Bean
272+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
273+
public Advisor customAuthorize(ReactiveAuthorizationManager<MethodInvocationResult> rules) {
274+
AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);
275+
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(pattern, rules);
276+
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
277+
return interceptor;
278+
}
279+
}
280+
----
281+
====
282+
283+
and it will be invoked after the `@PostAuthorize` interceptor.
284+
285+
== EnableReactiveMethodSecurity
286+
13287
[WARNING]
14288
====
15-
Method Security also supports Kotlin coroutines, though only to a limited degree.
289+
`@EnableReactiveMethodSecurity` also supports Kotlin coroutines, though only to a limited degree.
16290
When intercepting coroutines, only the first interceptor participates.
17-
If any other interceptors are present and come after Spring Security's method security interceptor, they will be skipped.
291+
If any other interceptors are present and come after Spring Security's method security interceptor, https://github.com/spring-projects/spring-framework/issues/22462[they will be skipped].
18292
====
19293

20294
====

0 commit comments

Comments
 (0)