Skip to content

Commit 9027409

Browse files
committed
Add MfaConfigurer
A configurer that extends the ability of any authentication configurer to participate as an additional authentication factor
1 parent 574318b commit 9027409

File tree

3 files changed

+326
-12
lines changed

3 files changed

+326
-12
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19+
import java.util.ArrayList;
20+
import java.util.Collection;
1921
import java.util.List;
2022
import java.util.function.Function;
2123
import java.util.function.Supplier;
@@ -28,11 +30,13 @@
2830
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
2931
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
3032
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
33+
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
3134
import org.springframework.security.authorization.AuthorityAuthorizationManager;
3235
import org.springframework.security.authorization.AuthorizationDecision;
3336
import org.springframework.security.authorization.AuthorizationEventPublisher;
3437
import org.springframework.security.authorization.AuthorizationManager;
3538
import org.springframework.security.authorization.AuthorizationManagers;
39+
import org.springframework.security.authorization.AuthorizationResult;
3640
import org.springframework.security.authorization.SingleResultAuthorizationManager;
3741
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
3842
import org.springframework.security.config.ObjectPostProcessor;
@@ -139,6 +143,8 @@ public final class AuthorizationManagerRequestMatcherRegistry
139143
private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager
140144
.builder();
141145

146+
private final HasAllAuthoritiesAuthorizationManager<RequestAuthorizationContext> hasAuthority = new HasAllAuthoritiesAuthorizationManager<>();
147+
142148
private List<RequestMatcher> unmappedMatchers;
143149

144150
private int mappingCount;
@@ -165,6 +171,7 @@ private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
165171
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
166172
Assert.state(this.mappingCount > 0,
167173
"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
174+
this.hasAuthority.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
168175
AuthorizationManager<HttpServletRequest> manager = postProcess(
169176
(AuthorizationManager<HttpServletRequest>) this.managerBuilder.build());
170177
return AuthorizeHttpRequestsConfigurer.this.postProcessor.postProcess(manager);
@@ -173,7 +180,7 @@ private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
173180
@Override
174181
protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
175182
this.unmappedMatchers = requestMatchers;
176-
return new AuthorizedUrl(requestMatchers);
183+
return new AuthorizedUrl(this, requestMatchers);
177184
}
178185

179186
/**
@@ -188,6 +195,10 @@ public AuthorizationManagerRequestMatcherRegistry withObjectPostProcessor(
188195
return this;
189196
}
190197

198+
void hasAuthority(String authority) {
199+
this.hasAuthority.add(authority);
200+
}
201+
191202
}
192203

193204
/**
@@ -199,6 +210,8 @@ public AuthorizationManagerRequestMatcherRegistry withObjectPostProcessor(
199210
*/
200211
public class AuthorizedUrl {
201212

213+
private final AuthorizationManagerRequestMatcherRegistry registry;
214+
202215
private final List<? extends RequestMatcher> matchers;
203216

204217
private boolean not;
@@ -207,7 +220,8 @@ public class AuthorizedUrl {
207220
* Creates an instance.
208221
* @param matchers the {@link RequestMatcher} instances to map
209222
*/
210-
AuthorizedUrl(List<? extends RequestMatcher> matchers) {
223+
AuthorizedUrl(AuthorizationManagerRequestMatcherRegistry registry, List<? extends RequestMatcher> matchers) {
224+
this.registry = registry;
211225
this.matchers = matchers;
212226
}
213227

@@ -289,10 +303,10 @@ public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... auth
289303
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
290304
}
291305

292-
private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
306+
private AuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
293307
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
294308
manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
295-
return manager;
309+
return withAuthentication(manager);
296310
}
297311

298312
/**
@@ -301,7 +315,7 @@ private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHiera
301315
* customizations
302316
*/
303317
public AuthorizationManagerRequestMatcherRegistry authenticated() {
304-
return access(AuthenticatedAuthorizationManager.authenticated());
318+
return access(withAuthentication(AuthenticatedAuthorizationManager.authenticated()));
305319
}
306320

307321
/**
@@ -313,7 +327,7 @@ public AuthorizationManagerRequestMatcherRegistry authenticated() {
313327
* @see RememberMeConfigurer
314328
*/
315329
public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
316-
return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
330+
return access(withAuthentication(AuthenticatedAuthorizationManager.fullyAuthenticated()));
317331
}
318332

319333
/**
@@ -324,7 +338,7 @@ public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
324338
* @see RememberMeConfigurer
325339
*/
326340
public AuthorizationManagerRequestMatcherRegistry rememberMe() {
327-
return access(AuthenticatedAuthorizationManager.rememberMe());
341+
return access(withAuthentication(AuthenticatedAuthorizationManager.rememberMe()));
328342
}
329343

330344
/**
@@ -366,6 +380,11 @@ public AuthorizationManagerRequestMatcherRegistry access(
366380
: AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
367381
}
368382

383+
private AuthorizationManager<RequestAuthorizationContext> withAuthentication(
384+
AuthorizationManager<RequestAuthorizationContext> manager) {
385+
return AuthorizationManagers.allOf(this.registry.hasAuthority, manager);
386+
}
387+
369388
/**
370389
* An object that allows configuring {@link RequestMatcher}s with URI path
371390
* variables
@@ -403,4 +422,25 @@ public AuthorizationManagerRequestMatcherRegistry equalTo(Function<Authenticatio
403422

404423
}
405424

425+
private static final class HasAllAuthoritiesAuthorizationManager<T> implements AuthorizationManager<T> {
426+
427+
private final AuthoritiesAuthorizationManager delegate = AuthoritiesAuthorizationManager.hasAllAuthorities();
428+
429+
private final Collection<String> authorities = new ArrayList<>();
430+
431+
@Override
432+
public AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
433+
return this.delegate.authorize(authentication, this.authorities);
434+
}
435+
436+
private void setRoleHierarchy(RoleHierarchy hierarchy) {
437+
this.delegate.setRoleHierarchy(hierarchy);
438+
}
439+
440+
private void add(String authority) {
441+
this.authorities.add(authority);
442+
}
443+
444+
}
445+
406446
}

config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19+
import java.util.ArrayList;
1920
import java.util.LinkedHashMap;
21+
import java.util.List;
22+
import java.util.function.Consumer;
2023

2124
import org.springframework.security.config.Customizer;
2225
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2326
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2427
import org.springframework.security.web.AuthenticationEntryPoint;
28+
import org.springframework.security.web.AuthorizationEntryPoint;
29+
import org.springframework.security.web.AuthorizationRequestingAccessDeniedHandler;
2530
import org.springframework.security.web.access.AccessDeniedHandler;
2631
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
2732
import org.springframework.security.web.access.ExceptionTranslationFilter;
@@ -75,13 +80,21 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
7580

7681
private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings = new LinkedHashMap<>();
7782

83+
private final List<AuthorizationEntryPoint> authorizationRequestEntries = new ArrayList<>();
84+
7885
/**
7986
* Creates a new instance
8087
* @see HttpSecurity#exceptionHandling(Customizer)
8188
*/
8289
public ExceptionHandlingConfigurer() {
8390
}
8491

92+
public ExceptionHandlingConfigurer<H> authorizationEntryPoint(
93+
Consumer<List<AuthorizationEntryPoint>> entriesConsumer) {
94+
entriesConsumer.accept(this.authorizationRequestEntries);
95+
return this;
96+
}
97+
8598
/**
8699
* Shortcut to specify the {@link AccessDeniedHandler} to be used is a specific error
87100
* page
@@ -203,7 +216,8 @@ public void configure(H http) {
203216
AccessDeniedHandler getAccessDeniedHandler(H http) {
204217
AccessDeniedHandler deniedHandler = this.accessDeniedHandler;
205218
if (deniedHandler == null) {
206-
deniedHandler = createDefaultDeniedHandler(http);
219+
deniedHandler = createAccessDeniedHandler(http);
220+
207221
}
208222
return deniedHandler;
209223
}
@@ -223,15 +237,26 @@ AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
223237
return entryPoint;
224238
}
225239

226-
private AccessDeniedHandler createDefaultDeniedHandler(H http) {
240+
private AccessDeniedHandler createAccessDeniedHandler(H http) {
241+
AccessDeniedHandler defaultAccessDeniedHandler = createDefaultAccessDeniedHandler();
227242
if (this.defaultDeniedHandlerMappings.isEmpty()) {
228-
return new AccessDeniedHandlerImpl();
243+
return defaultAccessDeniedHandler;
229244
}
230245
if (this.defaultDeniedHandlerMappings.size() == 1) {
231-
return this.defaultDeniedHandlerMappings.values().iterator().next();
246+
return defaultAccessDeniedHandler;
232247
}
233248
return new RequestMatcherDelegatingAccessDeniedHandler(this.defaultDeniedHandlerMappings,
234-
new AccessDeniedHandlerImpl());
249+
defaultAccessDeniedHandler);
250+
}
251+
252+
private AccessDeniedHandler createDefaultAccessDeniedHandler() {
253+
if (!this.authorizationRequestEntries.isEmpty()) {
254+
return new AuthorizationRequestingAccessDeniedHandler(this.authorizationRequestEntries);
255+
}
256+
if (this.defaultDeniedHandlerMappings.isEmpty()) {
257+
return new AccessDeniedHandlerImpl();
258+
}
259+
return this.defaultDeniedHandlerMappings.values().iterator().next();
235260
}
236261

237262
private AuthenticationEntryPoint createDefaultEntryPoint(H http) {

0 commit comments

Comments
 (0)