Skip to content

Commit 28aad88

Browse files
committed
Merge branch 'mfa'
Closes spring-projectsgh-2603
2 parents 549569e + bbba293 commit 28aad88

File tree

47 files changed

+3105
-21
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3105
-21
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>>
6868

6969
@Override
7070
public void init(H http) {
71+
this.loginPageGeneratingFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
7172
this.loginPageGeneratingFilter.setResolveHiddenInputs(DefaultLoginPageConfigurer.this::hiddenInputs);
7273
this.logoutPageGeneratingFilter.setResolveHiddenInputs(DefaultLoginPageConfigurer.this::hiddenInputs);
7374
http.setSharedObject(DefaultLoginPageGeneratingFilter.class, this.loginPageGeneratingFilter);

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717
package org.springframework.security.config.annotation.web.configurers;
1818

1919
import java.util.LinkedHashMap;
20+
import java.util.function.Consumer;
2021

2122
import org.jspecify.annotations.Nullable;
2223

2324
import org.springframework.security.config.Customizer;
2425
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2526
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
27+
import org.springframework.security.core.GrantedAuthority;
2628
import org.springframework.security.web.AuthenticationEntryPoint;
2729
import org.springframework.security.web.access.AccessDeniedHandler;
2830
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
31+
import org.springframework.security.web.access.DelegatingMissingAuthorityAccessDeniedHandler;
2932
import org.springframework.security.web.access.ExceptionTranslationFilter;
3033
import org.springframework.security.web.access.RequestMatcherDelegatingAccessDeniedHandler;
3134
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
@@ -77,6 +80,8 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
7780

7881
private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings = new LinkedHashMap<>();
7982

83+
private DelegatingMissingAuthorityAccessDeniedHandler.@Nullable Builder missingAuthoritiesHandlerBuilder;
84+
8085
/**
8186
* Creates a new instance
8287
* @see HttpSecurity#exceptionHandling(Customizer)
@@ -127,6 +132,43 @@ public ExceptionHandlingConfigurer<H> defaultAccessDeniedHandlerFor(AccessDenied
127132
return this;
128133
}
129134

135+
/**
136+
* Sets a default {@link AuthenticationEntryPoint} to be used which prefers being
137+
* invoked for the provided missing {@link GrantedAuthority}.
138+
* @param entryPoint the {@link AuthenticationEntryPoint} to use for the given
139+
* {@code authority}
140+
* @param authority the authority
141+
* @return the {@link ExceptionHandlingConfigurer} for further customizations
142+
* @since 7.0
143+
*/
144+
public ExceptionHandlingConfigurer<H> defaultDeniedHandlerForMissingAuthority(AuthenticationEntryPoint entryPoint,
145+
String authority) {
146+
if (this.missingAuthoritiesHandlerBuilder == null) {
147+
this.missingAuthoritiesHandlerBuilder = DelegatingMissingAuthorityAccessDeniedHandler.builder();
148+
}
149+
this.missingAuthoritiesHandlerBuilder.addEntryPointFor(entryPoint, authority);
150+
return this;
151+
}
152+
153+
/**
154+
* Sets a default {@link AuthenticationEntryPoint} to be used which prefers being
155+
* invoked for the provided missing {@link GrantedAuthority}.
156+
* @param entryPoint a consumer of a
157+
* {@link DelegatingAuthenticationEntryPoint.Builder} to use for the given
158+
* {@code authority}
159+
* @param authority the authority
160+
* @return the {@link ExceptionHandlingConfigurer} for further customizations
161+
* @since 7.0
162+
*/
163+
public ExceptionHandlingConfigurer<H> defaultDeniedHandlerForMissingAuthority(
164+
Consumer<DelegatingAuthenticationEntryPoint.Builder> entryPoint, String authority) {
165+
if (this.missingAuthoritiesHandlerBuilder == null) {
166+
this.missingAuthoritiesHandlerBuilder = DelegatingMissingAuthorityAccessDeniedHandler.builder();
167+
}
168+
this.missingAuthoritiesHandlerBuilder.addEntryPointFor(entryPoint, authority);
169+
return this;
170+
}
171+
130172
/**
131173
* Sets the {@link AuthenticationEntryPoint} to be used.
132174
*
@@ -229,6 +271,17 @@ AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
229271
}
230272

231273
private AccessDeniedHandler createDefaultDeniedHandler(H http) {
274+
AccessDeniedHandler defaults = createDefaultAccessDeniedHandler(http);
275+
if (this.missingAuthoritiesHandlerBuilder == null) {
276+
return defaults;
277+
}
278+
DelegatingMissingAuthorityAccessDeniedHandler deniedHandler = this.missingAuthoritiesHandlerBuilder.build();
279+
deniedHandler.setRequestCache(getRequestCache(http));
280+
deniedHandler.setDefaultAccessDeniedHandler(defaults);
281+
return deniedHandler;
282+
}
283+
284+
private AccessDeniedHandler createDefaultAccessDeniedHandler(H http) {
232285
if (this.defaultDeniedHandlerMappings.isEmpty()) {
233286
return new AccessDeniedHandlerImpl();
234287
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) {
231231
public void init(H http) throws Exception {
232232
super.init(http);
233233
initDefaultLoginFilter(http);
234+
ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
235+
if (exceptions != null) {
236+
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint();
237+
RequestMatcher requestMatcher = getAuthenticationEntryPointMatcher(http);
238+
exceptions.defaultDeniedHandlerForMissingAuthority((ep) -> ep.addEntryPointFor(entryPoint, requestMatcher),
239+
"FACTOR_PASSWORD");
240+
}
234241
}
235242

236243
@Override

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,10 @@ private void registerDefaultEntryPoint(B http, RequestMatcher preferredMatcher)
192192
if (exceptionHandling == null) {
193193
return;
194194
}
195-
exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(this.authenticationEntryPoint),
196-
preferredMatcher);
195+
AuthenticationEntryPoint entryPoint = postProcess(this.authenticationEntryPoint);
196+
exceptionHandling.defaultAuthenticationEntryPointFor(entryPoint, preferredMatcher);
197+
exceptionHandling.defaultDeniedHandlerForMissingAuthority(
198+
(ep) -> ep.addEntryPointFor(entryPoint, preferredMatcher), "FACTOR_PASSWORD");
197199
}
198200

199201
private void registerDefaultLogoutSuccessHandler(B http, RequestMatcher preferredMatcher) {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
import org.springframework.security.authentication.ProviderManager;
2828
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2929
import org.springframework.security.core.userdetails.UserDetailsService;
30+
import org.springframework.security.web.AuthenticationEntryPoint;
3031
import org.springframework.security.web.access.intercept.AuthorizationFilter;
32+
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
3133
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
3234
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
3335
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
3436
import org.springframework.security.web.csrf.CsrfToken;
37+
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
3538
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
3639
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
3740
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
@@ -150,6 +153,16 @@ public WebAuthnConfigurer<H> creationOptionsRepository(
150153
return this;
151154
}
152155

156+
@Override
157+
public void init(H http) throws Exception {
158+
ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
159+
if (exceptions != null) {
160+
AuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/login");
161+
exceptions.defaultDeniedHandlerForMissingAuthority(
162+
(ep) -> ep.addEntryPointFor(entryPoint, AnyRequestMatcher.INSTANCE), "FACTOR_WEBAUTHN");
163+
}
164+
}
165+
153166
@Override
154167
public void configure(H http) throws Exception {
155168
UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ public void init(H http) {
184184
.setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint());
185185
ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
186186
if (exceptions != null) {
187-
exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE);
187+
AuthenticationEntryPoint forbidden = new Http403ForbiddenEntryPoint();
188+
exceptions.defaultDeniedHandlerForMissingAuthority(
189+
(ep) -> ep.addEntryPointFor(forbidden, AnyRequestMatcher.INSTANCE), "FACTOR_X509");
188190
}
189191
}
190192

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4141
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
4242
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
43+
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
4344
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
4445
import org.springframework.security.context.DelegatingApplicationListener;
4546
import org.springframework.security.core.Authentication;
@@ -556,11 +557,18 @@ private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLogin
556557
RequestMatcher loginUrlMatcher = new AndRequestMatcher(notXRequestedWith,
557558
new NegatedRequestMatcher(defaultLoginPageMatcher), formLoginNotEnabled);
558559
// @formatter:off
559-
return DelegatingAuthenticationEntryPoint.builder()
560+
AuthenticationEntryPoint loginEntryPoint = DelegatingAuthenticationEntryPoint.builder()
560561
.addEntryPointFor(loginUrlEntryPoint, loginUrlMatcher)
561562
.defaultEntryPoint(getAuthenticationEntryPoint())
562563
.build();
563564
// @formatter:on
565+
ExceptionHandlingConfigurer<B> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
566+
if (exceptions != null) {
567+
RequestMatcher requestMatcher = getAuthenticationEntryPointMatcher(http);
568+
exceptions.defaultDeniedHandlerForMissingAuthority(
569+
(ep) -> ep.addEntryPointFor(loginEntryPoint, requestMatcher), "FACTOR_AUTHORIZATION_CODE");
570+
}
571+
return loginEntryPoint;
564572
}
565573

566574
private RequestMatcher getFormLoginNotEnabledRequestMatcher(B http) {

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ private void registerDefaultEntryPoint(H http) {
327327
RequestMatcher preferredMatcher = new OrRequestMatcher(
328328
Arrays.asList(this.requestMatcher, X_REQUESTED_WITH, restNotHtmlMatcher, allMatcher));
329329
exceptionHandling.defaultAuthenticationEntryPointFor(this.authenticationEntryPoint, preferredMatcher);
330+
exceptionHandling.defaultDeniedHandlerForMissingAuthority(
331+
(ep) -> ep.addEntryPointFor(this.authenticationEntryPoint, preferredMatcher), "FACTOR_BEARER");
330332
}
331333
}
332334

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3636
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
3737
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
38+
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
3839
import org.springframework.security.core.Authentication;
3940
import org.springframework.security.core.userdetails.UserDetailsService;
41+
import org.springframework.security.web.AuthenticationEntryPoint;
4042
import org.springframework.security.web.authentication.AuthenticationConverter;
4143
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
4244
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -134,6 +136,13 @@ public void init(H http) throws Exception {
134136
AuthenticationProvider authenticationProvider = getAuthenticationProvider();
135137
http.authenticationProvider(postProcess(authenticationProvider));
136138
intiDefaultLoginFilter(http);
139+
ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
140+
if (exceptions != null) {
141+
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint();
142+
RequestMatcher requestMatcher = getAuthenticationEntryPointMatcher(http);
143+
exceptions.defaultDeniedHandlerForMissingAuthority((ep) -> ep.addEntryPointFor(entryPoint, requestMatcher),
144+
"FACTOR_OTT");
145+
}
137146
}
138147

139148
private void intiDefaultLoginFilter(H http) {

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
3434
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
3535
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
36+
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
3637
import org.springframework.security.core.Authentication;
3738
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
3839
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
@@ -343,11 +344,18 @@ private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLogin
343344
RequestMatcher loginUrlMatcher = new AndRequestMatcher(notXRequestedWith,
344345
new NegatedRequestMatcher(defaultLoginPageMatcher));
345346
// @formatter:off
346-
return DelegatingAuthenticationEntryPoint.builder()
347+
AuthenticationEntryPoint loginEntryPoint = DelegatingAuthenticationEntryPoint.builder()
347348
.addEntryPointFor(loginUrlEntryPoint, loginUrlMatcher)
348349
.defaultEntryPoint(getAuthenticationEntryPoint())
349350
.build();
350351
// @formatter:on
352+
ExceptionHandlingConfigurer<B> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
353+
if (exceptions != null) {
354+
RequestMatcher requestMatcher = getAuthenticationEntryPointMatcher(http);
355+
exceptions.defaultDeniedHandlerForMissingAuthority(
356+
(ep) -> ep.addEntryPointFor(loginEntryPoint, requestMatcher), "FACTOR_SAML_RESPONSE");
357+
}
358+
return loginEntryPoint;
351359
}
352360

353361
private void setAuthenticationRequestRepository(B http,

0 commit comments

Comments
 (0)