Skip to content

Commit c5badbc

Browse files
committed
Add AccessDecisionManager Preparation Steps
Issue gh-11337
1 parent 86c9d5c commit c5badbc

File tree

2 files changed

+302
-0
lines changed

2 files changed

+302
-0
lines changed

docs/modules/ROOT/pages/migration.adoc

+301
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,98 @@ companion object {
367367
----
368368
====
369369

370+
==== Replace any custom method-security ``AccessDecisionManager``s
371+
372+
Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement.
373+
The preparation strategy will depend on your reason for each arrangement.
374+
Read on to find the best match for your situation.
375+
376+
===== I use `UnanimousBased`
377+
378+
If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`] with the default voters, you likely need do nothing since unanimous-based is the default behavior with {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`].
379+
380+
However, if you do discover that you cannot accept the default authorization managers, you can use `AuthorizationManagers.allOf` to compose your own arrangement.
381+
Having done that, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
382+
383+
===== I use `AffirmativeBased`
384+
385+
If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so:
386+
387+
====
388+
.Java
389+
[source,java,role="primary"]
390+
----
391+
AuthorizationManager<MethodInvocation> authorization = AuthorizationManagers.anyOf(
392+
// ... your list of authorization managers
393+
)
394+
----
395+
396+
.Kotlin
397+
[source,kotlin,role="secondary"]
398+
----
399+
val authorization = AuthorizationManagers.anyOf(
400+
// ... your list of authorization managers
401+
)
402+
----
403+
====
404+
405+
Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
406+
407+
===== I use `ConsensusBased`
408+
409+
There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`].
410+
In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account.
411+
412+
Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
413+
414+
===== I use a custom `AccessDecisionVoter`
415+
416+
You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter.
417+
418+
Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution.
419+
By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `@PreAuthorize` would look like:
420+
421+
====
422+
.Java
423+
[source,java,role="primary"]
424+
----
425+
public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager<MethodInvocation> {
426+
private final SecurityMetadataSource metadata;
427+
private final AccessDecisionVoter voter;
428+
429+
public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) {
430+
ExpressionBasedAnnotationAttributeFactory attributeFactory =
431+
new ExpressionBasedAnnotationAttributeFactory(expressionHandler);
432+
this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory);
433+
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
434+
expressionAdvice.setExpressionHandler(expressionHandler);
435+
this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice);
436+
}
437+
438+
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
439+
List<ConfigAttribute> attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis()));
440+
int decision = this.voter.vote(authentication.get(), invocation, attributes);
441+
if (decision == ACCESS_GRANTED) {
442+
return new AuthorizationDecision(true);
443+
}
444+
if (decision == ACCESS_DENIED) {
445+
return new AuthorizationDecision(false);
446+
}
447+
return null; // abstain
448+
}
449+
}
450+
----
451+
====
452+
453+
Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
454+
455+
===== I use a custom `AfterInvocationManager`
456+
457+
{security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] replaces both {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] and {security-api-url}org/springframework/security/access/intercept/AfterInvocationManager.html[`AfterInvocationManager`].
458+
The difference is that `AuthorizationManager<MethodInvocation>` replaces `AccessDecisionManager` and `AuthorizationManager<MethodInvocationResult>` replaces `AfterInvocationManager`.
459+
460+
Given that, <<_i_use_a_custom_accessdecisionvoter,the same rules apply for adaptation>>, where the goal this time is to implement `AuthorizationManager<MethodInvocationResult>` instead of `AuthorizationManager<MethodInvocation>` and use `AuthorizationManagerAfterMethodInterceptor` instead of `AuthorizationManagerBeforeMethodInterceptor`.
461+
370462
[[servlet-check-for-annotationconfigurationexceptions]]
371463
==== Check for ``AnnotationConfigurationException``s
372464

@@ -1099,6 +1191,215 @@ http {
10991191
----
11001192
====
11011193

1194+
==== Replace any custom filter-security ``AccessDecisionManager``s
1195+
1196+
Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement.
1197+
The preparation strategy will depend on your reason for each arrangement.
1198+
Read on to find the best match for your situation.
1199+
1200+
===== I use `UnanimousBased`
1201+
1202+
If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`], you should first adapt or replace any ``AccessDecisionVoter``s and then you can construct an `AuthorizationManager` like so:
1203+
1204+
====
1205+
.Java
1206+
[source,java,role="primary"]
1207+
----
1208+
@Bean
1209+
AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
1210+
PolicyAuthorizationManager policy = ...;
1211+
LocalAuthorizationManager local = ...;
1212+
return AuthorizationMangers.allOf(policy, local);
1213+
}
1214+
----
1215+
1216+
.Kotlin
1217+
[source,kotlin,role="secondary"]
1218+
----
1219+
@Bean
1220+
fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
1221+
val policy: PolicyAuthorizationManager = ...
1222+
val local: LocalAuthorizationManager = ...
1223+
return AuthorizationMangers.allOf(policy, local)
1224+
}
1225+
----
1226+
1227+
.Xml
1228+
[source,xml,role="secondary"]
1229+
----
1230+
<bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
1231+
factory-method="allOf">
1232+
<constructor-arg>
1233+
<util:list>
1234+
<bean class="my.PolicyAuthorizationManager"/>
1235+
<bean class="my.LocalAuthorizationManager"/>
1236+
</util:list>
1237+
</constructor-arg>
1238+
</bean>
1239+
----
1240+
====
1241+
1242+
then, wire it into the DSL like so:
1243+
1244+
====
1245+
.Java
1246+
[source,java,role="primary"]
1247+
----
1248+
http
1249+
.authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
1250+
// ...
1251+
----
1252+
1253+
.Kotlin
1254+
[source,kotlin,role="secondary"]
1255+
----
1256+
http {
1257+
authorizeHttpRequests {
1258+
authorize(anyRequest, requestAuthorization)
1259+
}
1260+
// ...
1261+
}
1262+
----
1263+
1264+
.Xml
1265+
[source,xml,role="secondary"]
1266+
----
1267+
<http authorization-manager-ref="requestAuthorization"/>
1268+
----
1269+
====
1270+
1271+
[NOTE]
1272+
====
1273+
`authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern.
1274+
See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details.
1275+
====
1276+
1277+
===== I use `AffirmativeBased`
1278+
1279+
If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so:
1280+
1281+
====
1282+
.Java
1283+
[source,java,role="primary"]
1284+
----
1285+
@Bean
1286+
AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
1287+
PolicyAuthorizationManager policy = ...;
1288+
LocalAuthorizationManager local = ...;
1289+
return AuthorizationMangers.anyOf(policy, local);
1290+
}
1291+
----
1292+
1293+
.Kotlin
1294+
[source,kotlin,role="secondary"]
1295+
----
1296+
@Bean
1297+
fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
1298+
val policy: PolicyAuthorizationManager = ...
1299+
val local: LocalAuthorizationManager = ...
1300+
return AuthorizationMangers.anyOf(policy, local)
1301+
}
1302+
----
1303+
1304+
.Xml
1305+
[source,xml,role="secondary"]
1306+
----
1307+
<bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
1308+
factory-method="anyOf">
1309+
<constructor-arg>
1310+
<util:list>
1311+
<bean class="my.PolicyAuthorizationManager"/>
1312+
<bean class="my.LocalAuthorizationManager"/>
1313+
</util:list>
1314+
</constructor-arg>
1315+
</bean>
1316+
----
1317+
====
1318+
1319+
then, wire it into the DSL like so:
1320+
1321+
====
1322+
.Java
1323+
[source,java,role="primary"]
1324+
----
1325+
http
1326+
.authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
1327+
// ...
1328+
----
1329+
1330+
.Kotlin
1331+
[source,kotlin,role="secondary"]
1332+
----
1333+
http {
1334+
authorizeHttpRequests {
1335+
authorize(anyRequest, requestAuthorization)
1336+
}
1337+
// ...
1338+
}
1339+
----
1340+
1341+
.Xml
1342+
[source,xml,role="secondary"]
1343+
----
1344+
<http authorization-manager-ref="requestAuthorization"/>
1345+
----
1346+
====
1347+
1348+
[NOTE]
1349+
====
1350+
`authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern.
1351+
See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details.
1352+
====
1353+
1354+
===== I use `ConsensusBased`
1355+
1356+
There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`].
1357+
In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account.
1358+
1359+
Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`].
1360+
1361+
===== I use a custom `AccessDecisionVoter`
1362+
1363+
You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter.
1364+
1365+
1366+
Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution.
1367+
By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `anyRequest().authenticated()` would look like:
1368+
1369+
====
1370+
.Java
1371+
[source,java,role="primary"]
1372+
----
1373+
public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager<RequestAuthorizationContext> {
1374+
private final SecurityMetadataSource metadata;
1375+
private final AccessDecisionVoter voter;
1376+
1377+
public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) {
1378+
Map<RequestMatcher, List<ConfigAttribute>> requestMap = Collections.singletonMap(
1379+
AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated")));
1380+
this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
1381+
WebExpressionVoter voter = new WebExpressionVoter();
1382+
voter.setExpressionHandler(expressionHandler);
1383+
this.voter = voter;
1384+
}
1385+
1386+
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
1387+
List<ConfigAttribute> attributes = this.metadata.getAttributes(context);
1388+
int decision = this.voter.vote(authentication.get(), invocation, attributes);
1389+
if (decision == ACCESS_GRANTED) {
1390+
return new AuthorizationDecision(true);
1391+
}
1392+
if (decision == ACCESS_DENIED) {
1393+
return new AuthorizationDecision(false);
1394+
}
1395+
return null; // abstain
1396+
}
1397+
}
1398+
----
1399+
====
1400+
1401+
Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`].
1402+
11021403
[[servlet-authorizationmanager-requests-opt-out]]
11031404
==== Opt-out Steps
11041405

docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationMan
131131

132132
You can also wire xref:servlet/authorization/architecture.adoc#authz-custom-authorization-manager[your own custom authorization managers] for any request matcher.
133133

134+
[[custom-authorization-manager]]
134135
Here is an example of mapping a custom authorization manager to the `my/authorized/endpoint`:
135136

136137
.Custom Authorization Manager

0 commit comments

Comments
 (0)