Skip to content

Commit 6e0527c

Browse files
committed
Add MultifactorAuthenticationToken
to indicate the state of principal which passed first step of multi step (factor) authentication.
1 parent 6ad328f commit 6e0527c

File tree

17 files changed

+778
-21
lines changed

17 files changed

+778
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.authentication.configurers.mfa;
18+
19+
import org.springframework.security.authentication.*;
20+
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
21+
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
22+
23+
/**
24+
* Allows configuring a {@link MultiFactorAuthenticationProvider}
25+
*
26+
* @param <B> the type of the {@link ProviderManagerBuilder}
27+
*
28+
* @author Yoshikazu Nojima
29+
*/
30+
public class MultiFactorAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>>
31+
extends SecurityConfigurerAdapter<AuthenticationManager, B> {
32+
33+
//~ Instance fields
34+
// ================================================================================================
35+
private AuthenticationProvider authenticationProvider;
36+
private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl();
37+
38+
/**
39+
* Constructor
40+
* @param authenticationProvider {@link AuthenticationProvider} to be delegated
41+
*/
42+
public MultiFactorAuthenticationProviderConfigurer(AuthenticationProvider authenticationProvider) {
43+
this.authenticationProvider = authenticationProvider;
44+
}
45+
46+
47+
public static MultiFactorAuthenticationProviderConfigurer multiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider){
48+
return new MultiFactorAuthenticationProviderConfigurer(authenticationProvider);
49+
}
50+
51+
@Override
52+
public void configure(B builder) {
53+
MultiFactorAuthenticationProvider multiFactorAuthenticationProvider = new MultiFactorAuthenticationProvider(authenticationProvider, mfaTokenEvaluator);
54+
multiFactorAuthenticationProvider = postProcess(multiFactorAuthenticationProvider);
55+
builder.authenticationProvider(multiFactorAuthenticationProvider);
56+
}
57+
58+
public MultiFactorAuthenticationProviderConfigurer<B> mfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) {
59+
this.mfaTokenEvaluator = mfaTokenEvaluator;
60+
return this;
61+
}
62+
}

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.springframework.security.authentication.AuthenticationTrustResolver;
4141
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
4242
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
43+
import org.springframework.security.authentication.MFATokenEvaluator;
44+
import org.springframework.security.authentication.MFATokenEvaluatorImpl;
4345
import org.springframework.security.config.annotation.ObjectPostProcessor;
4446
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
4547
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -113,6 +115,7 @@ public <T> T postProcess(T object) {
113115
private boolean authenticationManagerInitialized;
114116
private AuthenticationManager authenticationManager;
115117
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
118+
private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl();
116119
private HttpSecurity http;
117120
private boolean disableDefaults;
118121

@@ -390,6 +393,11 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
390393
this.trustResolver = trustResolver;
391394
}
392395

396+
@Autowired(required = false)
397+
public void setMfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){
398+
this.mfaTokenEvaluator = mfaTokenEvaluator;
399+
}
400+
393401
@Autowired(required = false)
394402
public void setContentNegotationStrategy(
395403
ContentNegotiationStrategy contentNegotiationStrategy) {
@@ -419,6 +427,7 @@ private Map<Class<?>, Object> createSharedObjects() {
419427
sharedObjects.put(ApplicationContext.class, context);
420428
sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy);
421429
sharedObjects.put(AuthenticationTrustResolver.class, trustResolver);
430+
sharedObjects.put(MFATokenEvaluator.class, mfaTokenEvaluator);
422431
return sharedObjects;
423432
}
424433

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
*/
1616
package org.springframework.security.config.annotation.web.configurers;
1717

18-
import java.util.LinkedHashMap;
19-
18+
import org.springframework.security.authentication.MFATokenEvaluator;
2019
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2120
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2221
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -30,6 +29,8 @@
3029
import org.springframework.security.web.savedrequest.RequestCache;
3130
import org.springframework.security.web.util.matcher.RequestMatcher;
3231

32+
import java.util.LinkedHashMap;
33+
3334
/**
3435
* Adds exception handling for Spring Security related exceptions to an application. All
3536
* properties have reasonable defaults, so no additional configuration is required other
@@ -67,6 +68,8 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
6768

6869
private AuthenticationEntryPoint authenticationEntryPoint;
6970

71+
private MFATokenEvaluator mfaTokenEvaluator;
72+
7073
private AccessDeniedHandler accessDeniedHandler;
7174

7275
private LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> defaultEntryPointMappings = new LinkedHashMap<>();
@@ -151,6 +154,18 @@ public ExceptionHandlingConfigurer<H> authenticationEntryPoint(
151154
return this;
152155
}
153156

157+
/**
158+
* Specifies the {@link MFATokenEvaluator} to be used
159+
*
160+
* @param mfaTokenEvaluator the {@link MFATokenEvaluator} to be used
161+
* @return the {@link ExceptionHandlingConfigurer} for further customization
162+
*/
163+
public ExceptionHandlingConfigurer<H> mfaTokenEvaluator(
164+
MFATokenEvaluator mfaTokenEvaluator) {
165+
this.mfaTokenEvaluator = mfaTokenEvaluator;
166+
return this;
167+
}
168+
154169
/**
155170
* Sets a default {@link AuthenticationEntryPoint} to be used which prefers being
156171
* invoked for the provided {@link RequestMatcher}. If only a single default
@@ -194,6 +209,9 @@ public void configure(H http) {
194209
entryPoint, getRequestCache(http));
195210
AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
196211
exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
212+
if (mfaTokenEvaluator != null) {
213+
exceptionTranslationFilter.setMFATokenEvaluator(mfaTokenEvaluator);
214+
}
197215
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
198216
http.addFilter(exceptionTranslationFilter);
199217
}

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,13 @@
1515
*/
1616
package org.springframework.security.config.annotation.web.configurers;
1717

18-
import java.util.ArrayList;
19-
import java.util.Arrays;
20-
import java.util.List;
21-
import javax.servlet.http.HttpServletResponse;
22-
import javax.servlet.http.HttpSession;
23-
2418
import org.springframework.context.ApplicationContext;
2519
import org.springframework.context.ApplicationListener;
2620
import org.springframework.context.event.GenericApplicationListenerAdapter;
2721
import org.springframework.context.event.SmartApplicationListener;
2822
import org.springframework.security.authentication.AuthenticationTrustResolver;
2923
import org.springframework.security.config.Customizer;
24+
import org.springframework.security.authentication.MFATokenEvaluator;
3025
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
3126
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3227
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -35,7 +30,6 @@
3530
import org.springframework.security.core.session.SessionRegistryImpl;
3631
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
3732
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
38-
import org.springframework.security.web.authentication.logout.LogoutHandler;
3933
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
4034
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
4135
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
@@ -48,15 +42,16 @@
4842
import org.springframework.security.web.context.SecurityContextRepository;
4943
import org.springframework.security.web.savedrequest.NullRequestCache;
5044
import org.springframework.security.web.savedrequest.RequestCache;
51-
import org.springframework.security.web.session.ConcurrentSessionFilter;
52-
import org.springframework.security.web.session.InvalidSessionStrategy;
53-
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
54-
import org.springframework.security.web.session.SessionManagementFilter;
55-
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
56-
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
45+
import org.springframework.security.web.session.*;
5746
import org.springframework.util.Assert;
5847
import org.springframework.util.CollectionUtils;
5948

49+
import javax.servlet.http.HttpServletResponse;
50+
import javax.servlet.http.HttpSession;
51+
import java.util.ArrayList;
52+
import java.util.Arrays;
53+
import java.util.List;
54+
6055
/**
6156
* Allows configuring session management.
6257
*
@@ -471,6 +466,11 @@ public void init(H http) {
471466
if (trustResolver != null) {
472467
httpSecurityRepository.setTrustResolver(trustResolver);
473468
}
469+
MFATokenEvaluator mfaTokenEvaluator = http
470+
.getSharedObject(MFATokenEvaluator.class);
471+
if (mfaTokenEvaluator != null) {
472+
httpSecurityRepository.setMFATokenEvaluator(mfaTokenEvaluator);
473+
}
474474
http.setSharedObject(SecurityContextRepository.class,
475475
httpSecurityRepository);
476476
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.authentication.configurers.mfa;
18+
19+
import org.junit.Test;
20+
import org.mockito.ArgumentCaptor;
21+
import org.springframework.security.authentication.AuthenticationProvider;
22+
import org.springframework.security.authentication.MFATokenEvaluator;
23+
import org.springframework.security.authentication.MultiFactorAuthenticationProvider;
24+
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.verify;
29+
import static org.springframework.security.config.annotation.authentication.configurers.mfa.MultiFactorAuthenticationProviderConfigurer.multiFactorAuthenticationProvider;
30+
31+
public class MultiFactorAuthenticationProviderConfigurerTests {
32+
33+
@Test
34+
public void test(){
35+
AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class);
36+
MFATokenEvaluator mfaTokenEvaluator = mock(MFATokenEvaluator.class);
37+
MultiFactorAuthenticationProviderConfigurer configurer
38+
= multiFactorAuthenticationProvider(delegatedAuthenticationProvider);
39+
configurer.mfaTokenEvaluator(mfaTokenEvaluator);
40+
ProviderManagerBuilder providerManagerBuilder = mock(ProviderManagerBuilder.class);
41+
configurer.configure(providerManagerBuilder);
42+
ArgumentCaptor<AuthenticationProvider> argumentCaptor = ArgumentCaptor.forClass(AuthenticationProvider.class);
43+
verify(providerManagerBuilder).authenticationProvider(argumentCaptor.capture());
44+
MultiFactorAuthenticationProvider authenticationProvider = (MultiFactorAuthenticationProvider) argumentCaptor.getValue();
45+
46+
assertThat(authenticationProvider.getAuthenticationProvider()).isEqualTo(delegatedAuthenticationProvider);
47+
assertThat(authenticationProvider.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator);
48+
}
49+
}

core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
3636
private Class<? extends Authentication> anonymousClass = AnonymousAuthenticationToken.class;
3737
private Class<? extends Authentication> rememberMeClass = RememberMeAuthenticationToken.class;
3838

39+
private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl();
40+
3941
// ~ Methods
4042
// ========================================================================================================
4143

@@ -52,6 +54,10 @@ public boolean isAnonymous(Authentication authentication) {
5254
return false;
5355
}
5456

57+
if (mfaTokenEvaluator != null && mfaTokenEvaluator.isMultiFactorAuthentication(authentication)) {
58+
return true;
59+
}
60+
5561
return anonymousClass.isAssignableFrom(authentication.getClass());
5662
}
5763

@@ -70,4 +76,8 @@ public void setAnonymousClass(Class<? extends Authentication> anonymousClass) {
7076
public void setRememberMeClass(Class<? extends Authentication> rememberMeClass) {
7177
this.rememberMeClass = rememberMeClass;
7278
}
79+
80+
public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){
81+
this.mfaTokenEvaluator = mfaTokenEvaluator;
82+
}
7383
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authentication;
18+
19+
import org.springframework.security.core.Authentication;
20+
21+
/**
22+
* Evaluates <code>Authentication</code> tokens
23+
*
24+
* @author Yoshikazu Nojima
25+
*/
26+
public interface MFATokenEvaluator {
27+
28+
/**
29+
* Indicates whether the passed <code>Authentication</code> token represents a
30+
* user in the middle of multi factor authentication process.
31+
*
32+
* @param authentication to test (may be <code>null</code> in which case the method
33+
* will always return <code>false</code>)
34+
*
35+
* @return <code>true</code> the passed authentication token represented a principal
36+
* in the middle of multi factor authentication process, <code>false</code> otherwise
37+
*/
38+
boolean isMultiFactorAuthentication(Authentication authentication);
39+
40+
/**
41+
* Indicates whether the principal associated with the <code>Authentication</code>
42+
* token is allowed to login with only single factor.
43+
*
44+
* @param authentication to test (may be <code>null</code> in which case the method
45+
* will always return <code>false</code>)
46+
*
47+
* @return <code>true</code> the principal associated with thepassed authentication
48+
* token is allowed to login with only single factor, <code>false</code> otherwise
49+
*/
50+
boolean isSingleFactorAuthenticationAllowed(Authentication authentication);
51+
}

0 commit comments

Comments
 (0)