Skip to content

Commit 1608465

Browse files
committed
DefaultAuthorizationManagerFactory additionalAuthorization
This commit adds AuthorizationManager<T> additionalAuthorization to DefaultAuthorizationManagerFactory which can be used for multi factor authorization. There is a builder that allows for creating an instance that requires static additional authorities, but for more advanced cases users can inject an additionalAuthorization that looks up if the user has settings that enable additional required authorities. The builder can later be updated to support checking that a particular authority was granted within a specified amount of time. Issue spring-projectsgh-17900
1 parent 68742e1 commit 1608465

File tree

3 files changed

+369
-50
lines changed

3 files changed

+369
-50
lines changed

core/src/main/java/org/springframework/security/authorization/AllAuthoritiesAuthorizationManager.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ public static <T> AllAuthoritiesAuthorizationManager<T> hasAllAuthorities(String
134134
return new AllAuthoritiesAuthorizationManager<>(authorities);
135135
}
136136

137+
/**
138+
* Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
139+
* authorities.
140+
* @param authorities the authorities to check for
141+
* @param <T> the type of object being authorized
142+
* @return the new instance
143+
*/
144+
public static <T> AllAuthoritiesAuthorizationManager<T> hasAllAuthorities(List<String> authorities) {
145+
Assert.notEmpty(authorities, "authorities cannot be empty");
146+
Assert.noNullElements(authorities, "authorities cannot contain null values");
147+
return new AllAuthoritiesAuthorizationManager<>(authorities.toArray(new String[0]));
148+
}
149+
137150
private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
138151
String[] result = new String[roles.length];
139152
for (int i = 0; i < roles.length; i++) {

core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java

Lines changed: 116 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@
1616

1717
package org.springframework.security.authorization;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
21+
1922
import org.jspecify.annotations.Nullable;
2023

2124
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
2225
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
2326
import org.springframework.security.authentication.AuthenticationTrustResolver;
2427
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
2528
import org.springframework.util.Assert;
29+
import org.springframework.util.CollectionUtils;
2630

2731
/**
2832
* A factory for creating different kinds of {@link AuthorizationManager} instances.
2933
*
3034
* @param <T> the type of object that the authorization check is being done on
3135
* @author Steve Riesenberg
3236
* @author Andrey Litvitski
37+
* @author Rob Winch
3338
* @since 7.0
3439
*/
3540
public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object>
@@ -41,7 +46,7 @@ public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object
4146

4247
private String rolePrefix = "ROLE_";
4348

44-
private String[] requiredAuthorities = new String[0];
49+
private @Nullable AuthorizationManager<T> additionalAuthorization;
4550

4651
/**
4752
* Sets the {@link AuthenticationTrustResolver} used to check the user's
@@ -73,32 +78,29 @@ public void setRolePrefix(String rolePrefix) {
7378
}
7479

7580
/**
76-
* Sets authorities required for authorization managers that apply to authenticated
77-
* users.
78-
* <p>
79-
* Does not affect {@code anonymous}, {@code permitAll}, or {@code denyAll}.
80-
* <p>
81-
* Evaluated with the configured {@link RoleHierarchy}.
82-
* @param requiredAuthorities the required authorities (must not be {@code null})
83-
*/
84-
public void setRequiredAuthorities(String[] requiredAuthorities) {
85-
Assert.notNull(requiredAuthorities, "requiredAuthorities cannot be null");
86-
this.requiredAuthorities = requiredAuthorities;
87-
}
88-
89-
/**
90-
* Creates a factory that requires the given authorities for authorization managers
91-
* that apply to authenticated users.
81+
* Sets additional authorization to be applied to the returned
82+
* {@link AuthorizationManager} for the following methods:
83+
*
84+
* <ul>
85+
* <li>{@link #hasRole(String)}</li>
86+
* <li>{@link #hasAnyRole(String...)}</li>
87+
* <li>{@link #hasAllRoles(String...)}</li>
88+
* <li>{@link #hasAuthority(String)}</li>
89+
* <li>{@link #hasAnyAuthority(String...)}</li>
90+
* <li>{@link #hasAllAuthorities(String...)}</li>
91+
* <li>{@link #authenticated()}</li>
92+
* <li>{@link #fullyAuthenticated()}</li>
93+
* <li>{@link #rememberMe()}</li>
94+
* </ul>
95+
*
9296
* <p>
93-
* Does not affect {@code anonymous}, {@code permitAll}, or {@code denyAll}.
94-
* @param authorities the required authorities
95-
* @param <T> the secured object type
96-
* @return a factory configured with the required authorities
97+
* This does not affect {@code anonymous}, {@code permitAll}, or {@code denyAll}.
98+
* </p>
99+
* @param additionalAuthorization the {@link AuthorizationManager} to be applied.
100+
* Default is null (no additional authorization).
97101
*/
98-
public static <T> AuthorizationManagerFactory<T> withAuthorities(String... authorities) {
99-
DefaultAuthorizationManagerFactory<T> factory = new DefaultAuthorizationManagerFactory<>();
100-
factory.setRequiredAuthorities(authorities);
101-
return factory;
102+
public void setAdditionalAuthorization(@Nullable AuthorizationManager<T> additionalAuthorization) {
103+
this.additionalAuthorization = additionalAuthorization;
102104
}
103105

104106
@Override
@@ -108,76 +110,140 @@ public AuthorizationManager<T> hasRole(String role) {
108110

109111
@Override
110112
public AuthorizationManager<T> hasAnyRole(String... roles) {
111-
return withRequiredAuthorities(
112-
withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles)));
113+
return createManager(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles));
113114
}
114115

115116
@Override
116117
public AuthorizationManager<T> hasAllRoles(String... roles) {
117-
return withRequiredAuthorities(withRoleHierarchy(
118-
AllAuthoritiesAuthorizationManager.hasAllPrefixedAuthorities(this.rolePrefix, roles)));
118+
return createManager(AllAuthoritiesAuthorizationManager.hasAllPrefixedAuthorities(this.rolePrefix, roles));
119119
}
120120

121121
@Override
122122
public AuthorizationManager<T> hasAuthority(String authority) {
123-
return withRequiredAuthorities(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
123+
return createManager(AuthorityAuthorizationManager.hasAuthority(authority));
124124
}
125125

126126
@Override
127127
public AuthorizationManager<T> hasAnyAuthority(String... authorities) {
128-
return withRequiredAuthorities(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
128+
return createManager(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
129129
}
130130

131131
@Override
132132
public AuthorizationManager<T> hasAllAuthorities(String... authorities) {
133-
return withRequiredAuthorities(
134-
withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities)));
133+
return createManager(AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities));
135134
}
136135

137136
@Override
138137
public AuthorizationManager<T> authenticated() {
139-
return withRequiredAuthorities(withTrustResolver(AuthenticatedAuthorizationManager.authenticated()));
138+
return createManager(AuthenticatedAuthorizationManager.authenticated());
140139
}
141140

142141
@Override
143142
public AuthorizationManager<T> fullyAuthenticated() {
144-
return withRequiredAuthorities(withTrustResolver(AuthenticatedAuthorizationManager.fullyAuthenticated()));
143+
return createManager(AuthenticatedAuthorizationManager.fullyAuthenticated());
145144
}
146145

147146
@Override
148147
public AuthorizationManager<T> rememberMe() {
149-
return withRequiredAuthorities(withTrustResolver(AuthenticatedAuthorizationManager.rememberMe()));
148+
return createManager(AuthenticatedAuthorizationManager.rememberMe());
150149
}
151150

152151
@Override
153152
public AuthorizationManager<T> anonymous() {
154-
return withTrustResolver(AuthenticatedAuthorizationManager.anonymous());
153+
return createManager(AuthenticatedAuthorizationManager.anonymous());
155154
}
156155

157-
private AuthorityAuthorizationManager<T> withRoleHierarchy(AuthorityAuthorizationManager<T> authorizationManager) {
156+
/**
157+
* Creates a {@link Builder} that helps build an {@link AuthorizationManager} to set
158+
* on {@link #setAdditionalAuthorization(AuthorizationManager)} for common scenarios.
159+
* <p>
160+
* Does not affect {@code anonymous}, {@code permitAll}, or {@code denyAll}.
161+
* @param <T> the secured object type
162+
* @return a factory configured with the required authorities
163+
*/
164+
public static <T> Builder<T> builder() {
165+
return new Builder<>();
166+
}
167+
168+
private AuthorizationManager<T> createManager(AuthorityAuthorizationManager<T> authorizationManager) {
158169
authorizationManager.setRoleHierarchy(this.roleHierarchy);
159-
return authorizationManager;
170+
return withAdditionalAuthorization(authorizationManager);
160171
}
161172

162-
private AllAuthoritiesAuthorizationManager<T> withRoleHierarchy(
163-
AllAuthoritiesAuthorizationManager<T> authorizationManager) {
173+
private AuthorizationManager<T> createManager(AllAuthoritiesAuthorizationManager<T> authorizationManager) {
164174
authorizationManager.setRoleHierarchy(this.roleHierarchy);
165-
return authorizationManager;
175+
return withAdditionalAuthorization(authorizationManager);
166176
}
167177

168-
private AuthenticatedAuthorizationManager<T> withTrustResolver(
169-
AuthenticatedAuthorizationManager<T> authorizationManager) {
178+
private AuthorizationManager<T> createManager(AuthenticatedAuthorizationManager<T> authorizationManager) {
170179
authorizationManager.setTrustResolver(this.trustResolver);
171-
return authorizationManager;
180+
return withAdditionalAuthorization(authorizationManager);
172181
}
173182

174-
private AuthorizationManager<T> withRequiredAuthorities(AuthorizationManager<T> manager) {
175-
if (this.requiredAuthorities == null || this.requiredAuthorities.length == 0) {
183+
private AuthorizationManager<T> withAdditionalAuthorization(AuthorizationManager<T> manager) {
184+
if (this.additionalAuthorization == null) {
176185
return manager;
177186
}
178-
AuthorizationManager<T> required = withRoleHierarchy(
179-
AllAuthoritiesAuthorizationManager.hasAllAuthorities(this.requiredAuthorities));
180-
return AuthorizationManagers.allOf(new AuthorizationDecision(false), manager, required);
187+
return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.additionalAuthorization, manager);
188+
}
189+
190+
/**
191+
* A builder that allows creating {@link DefaultAuthorizationManagerFactory} with
192+
* additional authorization for common scenarios.
193+
*
194+
* @param <T> the type for the {@link DefaultAuthorizationManagerFactory}
195+
* @author Rob Winch
196+
*/
197+
public static final class Builder<T> {
198+
199+
private final List<String> additionalAuthorities = new ArrayList<>();
200+
201+
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
202+
203+
/**
204+
* Add additional authorities that will be required.
205+
* @param additionalAuthorities the additional authorities.
206+
* @return the {@link Builder} to further customize.
207+
*/
208+
public Builder<T> requireAdditionalAuthorities(String... additionalAuthorities) {
209+
Assert.notEmpty(additionalAuthorities, "additionalAuthorities cannot be empty");
210+
for (String additionalAuthority : additionalAuthorities) {
211+
this.additionalAuthorities.add(additionalAuthority);
212+
}
213+
return this;
214+
}
215+
216+
/**
217+
* The {@link RoleHierarchy} to use.
218+
* @param roleHierarchy the non-null {@link RoleHierarchy} to use. Default is
219+
* {@link NullRoleHierarchy}.
220+
* @return the Builder to further customize.
221+
*/
222+
public Builder<T> roleHierarchy(RoleHierarchy roleHierarchy) {
223+
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
224+
this.roleHierarchy = roleHierarchy;
225+
return this;
226+
}
227+
228+
/**
229+
* Builds a {@link DefaultAuthorizationManagerFactory} that has the
230+
* {@link #setAdditionalAuthorization(AuthorizationManager)} set.
231+
* @return the {@link DefaultAuthorizationManagerFactory}.
232+
*/
233+
public DefaultAuthorizationManagerFactory<T> build() {
234+
Assert.state(!CollectionUtils.isEmpty(this.additionalAuthorities), "additionalAuthorities cannot be empty");
235+
DefaultAuthorizationManagerFactory<T> result = new DefaultAuthorizationManagerFactory<>();
236+
AllAuthoritiesAuthorizationManager<T> additionalChecks = AllAuthoritiesAuthorizationManager
237+
.hasAllAuthorities(this.additionalAuthorities);
238+
result.setRoleHierarchy(this.roleHierarchy);
239+
additionalChecks.setRoleHierarchy(this.roleHierarchy);
240+
result.setAdditionalAuthorization(additionalChecks);
241+
return result;
242+
}
243+
244+
private Builder() {
245+
}
246+
181247
}
182248

183249
}

0 commit comments

Comments
 (0)