Skip to content

Commit dadf108

Browse files
committed
Add WebExpressionAuthorizationManager.Builder
Closes gh-17504
1 parent c312d18 commit dadf108

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,29 @@ Kotlin::
996996
----
997997
======
998998

999+
To migrate several, you can use `WebExpressionAuthorizationManager#withDefaults`:
1000+
1001+
[tabs]
1002+
======
1003+
Java::
1004+
+
1005+
[source,java,role="primary"]
1006+
----
1007+
WebExpressionAuthorizationManager.Builder authz = WebExpressionAuthorizationManager.withDefaults();
1008+
.requestMatchers("/test/**").access(authz.expression("hasRole('ADMIN') && hasRole('USER')"))
1009+
.requestMatchers("/test/**").access(authz.expression("permitAll"))
1010+
----
1011+
1012+
Kotlin::
1013+
+
1014+
[source,kotlin,role="secondary"]
1015+
----
1016+
var authz = WebExpressionAuthorizationManager.withDefaults()
1017+
.requestMatchers("/test/**").access(authz.expression("hasRole('ADMIN') && hasRole('USER')"))
1018+
.requestMatchers("/test/**").access(authz.expression("permitAll"))
1019+
----
1020+
======
1021+
9991022
If you are referring to a bean in your expression like so: `@webSecurity.check(authentication, request)`, it's recommended that you instead call the bean directly, which will look something like the following:
10001023

10011024
[tabs]
@@ -1019,7 +1042,32 @@ Kotlin::
10191042

10201043
For complex instructions that include bean references as well as other expressions, it is recommended that you change those to implement `AuthorizationManager` and refer to them by calling `.access(AuthorizationManager)`.
10211044

1022-
If you are not able to do that, you can configure a javadoc:org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler[] with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.
1045+
If you are not able to do that, you can publish javadoc:org.springframework.security.web.access.expression.WebExpressionAuthorizationManager$Builder[] as a bean:
1046+
1047+
[tabs]
1048+
======
1049+
Java::
1050+
+
1051+
[source,java,role="primary"]
1052+
----
1053+
@Bean
1054+
WebExpressionAuthorizationManager.Builder authz() {
1055+
return WebExpressionAuthorizationManager.withDefaults();
1056+
}
1057+
----
1058+
1059+
Kotlin::
1060+
+
1061+
[source,kotlin,role="secondary"]
1062+
----
1063+
@Bean
1064+
fun authz(): WebExpressionAuthorizationManager.Builder {
1065+
return WebExpressionAuthorizationManager.withDefaults()
1066+
}
1067+
----
1068+
======
1069+
1070+
Then, expressions passed to that builder will be able to refer to beans.
10231071

10241072
[[security-matchers]]
10251073
== Security Matchers

web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import java.util.function.Supplier;
2020

21+
import org.springframework.beans.BeansException;
22+
import org.springframework.context.ApplicationContext;
23+
import org.springframework.context.ApplicationContextAware;
2124
import org.springframework.expression.EvaluationContext;
2225
import org.springframework.expression.Expression;
2326
import org.springframework.security.access.expression.ExpressionUtils;
@@ -51,11 +54,20 @@ public WebExpressionAuthorizationManager(String expressionString) {
5154
this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
5255
}
5356

57+
private WebExpressionAuthorizationManager(String expressionString,
58+
SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler) {
59+
Assert.hasText(expressionString, "expressionString cannot be empty");
60+
this.expressionHandler = expressionHandler;
61+
this.expression = expressionHandler.getExpressionParser().parseExpression(expressionString);
62+
}
63+
5464
/**
5565
* Sets the {@link SecurityExpressionHandler} to be used. The default is
5666
* {@link DefaultHttpSecurityExpressionHandler}.
5767
* @param expressionHandler the {@link SecurityExpressionHandler} to use
68+
* @deprecated Please use {@link #withDefaults()} or {@link #withExpressionHandler}
5869
*/
70+
@Deprecated
5971
public void setExpressionHandler(SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler) {
6072
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
6173
this.expressionHandler = expressionHandler;
@@ -82,4 +94,78 @@ public String toString() {
8294
return "WebExpressionAuthorizationManager[expression='" + this.expression + "']";
8395
}
8496

97+
/**
98+
* Use a {@link DefaultHttpSecurityExpressionHandler} to create
99+
* {@link WebExpressionAuthorizationManager} instances.
100+
*
101+
* <p>
102+
* Note that publishing the {@link Builder} as a bean will allow the default
103+
* expression handler to be configured with a bean provider so that expressions can
104+
* reference beans
105+
* @return a {@link Builder} for constructing
106+
* {@link WebExpressionAuthorizationManager} instances
107+
* @since 7.0
108+
*/
109+
public static Builder withDefaults() {
110+
return new Builder();
111+
}
112+
113+
/**
114+
* Use this {@link SecurityExpressionHandler} to create
115+
* {@link WebExpressionAuthorizationManager} instances
116+
* @param expressionHandler
117+
* @return a {@link Builder} for constructing
118+
* {@link WebExpressionAuthorizationManager} instances
119+
* @since 7.0
120+
*/
121+
public static Builder withExpressionHandler(
122+
SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler) {
123+
return new Builder(expressionHandler);
124+
}
125+
126+
/**
127+
* A {@link Builder} for constructing {@link WebExpressionAuthorizationManager}
128+
* instances.
129+
*
130+
* <p>
131+
* May be reused to create multiple instances.
132+
*
133+
* @author Josh Cummings
134+
* @since 7.0
135+
*/
136+
public static final class Builder implements ApplicationContextAware {
137+
138+
private final SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler;
139+
140+
private final boolean defaultExpressionHandler;
141+
142+
private Builder() {
143+
this.expressionHandler = new DefaultHttpSecurityExpressionHandler();
144+
this.defaultExpressionHandler = true;
145+
}
146+
147+
private Builder(SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler) {
148+
this.expressionHandler = expressionHandler;
149+
this.defaultExpressionHandler = false;
150+
}
151+
152+
/**
153+
* Create a {@link WebExpressionAuthorizationManager} using this
154+
* {@code expression}
155+
* @param expression the expression to evaluate
156+
* @return the resulting {@link AuthorizationManager}
157+
*/
158+
public WebExpressionAuthorizationManager expression(String expression) {
159+
return new WebExpressionAuthorizationManager(expression, this.expressionHandler);
160+
}
161+
162+
@Override
163+
public void setApplicationContext(ApplicationContext context) throws BeansException {
164+
if (this.defaultExpressionHandler) {
165+
((DefaultHttpSecurityExpressionHandler) this.expressionHandler).setApplicationContext(context);
166+
}
167+
}
168+
169+
}
170+
85171
}

web/src/test/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManagerTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.context.support.GenericApplicationContext;
2122
import org.springframework.expression.Expression;
2223
import org.springframework.expression.ExpressionParser;
2324
import org.springframework.mock.web.MockHttpServletRequest;
@@ -102,4 +103,49 @@ void checkWhenExpressionHasRoleAdminConfiguredAndRoleUserThenDeniedDecision() {
102103
assertThat(decision.isGranted()).isFalse();
103104
}
104105

106+
@Test
107+
void authorizeWhenDefaultsThenEvaluatesExpressionsReferencingBeans() {
108+
GenericApplicationContext context = new GenericApplicationContext();
109+
context.registerBean("bean", WebExpressionAuthorizationManagerTests.class, () -> this);
110+
context.refresh();
111+
WebExpressionAuthorizationManager.Builder builder = WebExpressionAuthorizationManager.withDefaults();
112+
builder.setApplicationContext(context);
113+
WebExpressionAuthorizationManager manager = builder
114+
.expression("@bean.class.simpleName.startsWith('WebExpression')");
115+
AuthorizationResult result = manager.authorize(TestAuthentication::authenticatedUser,
116+
new RequestAuthorizationContext(new MockHttpServletRequest()));
117+
assertThat(result.isGranted()).isTrue();
118+
}
119+
120+
@Test
121+
void authorizeWhenDefaultsAsBeanThenEvaluatesExpressionsReferencingBeans() {
122+
GenericApplicationContext context = new GenericApplicationContext();
123+
context.registerBean("bean", WebExpressionAuthorizationManagerTests.class, () -> this);
124+
context.registerBean("builder", WebExpressionAuthorizationManager.Builder.class,
125+
WebExpressionAuthorizationManager::withDefaults);
126+
context.refresh();
127+
WebExpressionAuthorizationManager.Builder builder = context
128+
.getBean(WebExpressionAuthorizationManager.Builder.class);
129+
WebExpressionAuthorizationManager manager = builder
130+
.expression("@bean.class.simpleName.startsWith('WebExpression')");
131+
AuthorizationResult result = manager.authorize(TestAuthentication::authenticatedUser,
132+
new RequestAuthorizationContext(new MockHttpServletRequest()));
133+
assertThat(result.isGranted()).isTrue();
134+
}
135+
136+
@Test
137+
void authorizeWhenExpressionHandlerHasBeanProviderThenEvaluatesExpressionsReferencingBeans() {
138+
GenericApplicationContext context = new GenericApplicationContext();
139+
context.registerBean("bean", WebExpressionAuthorizationManagerTests.class, () -> this);
140+
context.refresh();
141+
DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler();
142+
expressionHandler.setApplicationContext(context);
143+
WebExpressionAuthorizationManager manager = WebExpressionAuthorizationManager
144+
.withExpressionHandler(expressionHandler)
145+
.expression("@bean.class.simpleName.startsWith('WebExpression')");
146+
AuthorizationResult result = manager.authorize(TestAuthentication::authenticatedUser,
147+
new RequestAuthorizationContext(new MockHttpServletRequest()));
148+
assertThat(result.isGranted()).isTrue();
149+
}
150+
105151
}

0 commit comments

Comments
 (0)