Skip to content

Commit f1ccf2f

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

File tree

12 files changed

+401
-6
lines changed

12 files changed

+401
-6
lines changed

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

@@ -391,6 +394,11 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
391394
this.trustResolver = trustResolver;
392395
}
393396

397+
@Autowired(required = false)
398+
public void setMfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){
399+
this.mfaTokenEvaluator = mfaTokenEvaluator;
400+
}
401+
394402
@Autowired(required = false)
395403
public void setContentNegotationStrategy(
396404
ContentNegotiationStrategy contentNegotiationStrategy) {
@@ -420,6 +428,7 @@ private Map<Class<? extends Object>, Object> createSharedObjects() {
420428
sharedObjects.put(ApplicationContext.class, context);
421429
sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy);
422430
sharedObjects.put(AuthenticationTrustResolver.class, trustResolver);
431+
sharedObjects.put(MFATokenEvaluator.class, mfaTokenEvaluator);
423432
return sharedObjects;
424433
}
425434

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

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

1818
import java.util.LinkedHashMap;
1919

20+
import org.springframework.security.authentication.MFATokenEvaluator;
2021
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2122
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2223
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -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) throws Exception {
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.context.event.GenericApplicationListenerAdapter;
2727
import org.springframework.context.event.SmartApplicationListener;
2828
import org.springframework.security.authentication.AuthenticationTrustResolver;
29+
import org.springframework.security.authentication.MFATokenEvaluator;
2930
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
3031
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3132
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -425,6 +426,11 @@ public void init(H http) throws Exception {
425426
if (trustResolver != null) {
426427
httpSecurityRepository.setTrustResolver(trustResolver);
427428
}
429+
MFATokenEvaluator mfaTokenEvaluator = http
430+
.getSharedObject(MFATokenEvaluator.class);
431+
if(mfaTokenEvaluator != null){
432+
httpSecurityRepository.setMFATokenEvaluator(mfaTokenEvaluator);
433+
}
428434
http.setSharedObject(SecurityContextRepository.class,
429435
httpSecurityRepository);
430436
}

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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
* http://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+
}
Lines changed: 49 additions & 0 deletions
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+
* http://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+
* Basic implementation of {@link MFATokenEvaluator}.
23+
* <p>
24+
* Makes trust decisions based on whether the passed <code>Authentication</code> is an
25+
* instance of a defined class.
26+
* <p>
27+
* If {@link #multiFactorClass} is <code>null</code>, the
28+
* corresponding method will always return <code>false</code>.
29+
*
30+
* @author Yoshikazu Nojima
31+
*/
32+
public class MFATokenEvaluatorImpl implements MFATokenEvaluator {
33+
34+
private Class<? extends Authentication> multiFactorClass = MultiFactorAuthenticationToken.class;
35+
36+
Class<? extends Authentication> getMultiFactorClass() { return multiFactorClass; }
37+
38+
@Override
39+
public boolean isMultiFactorAuthentication(Authentication authentication) {
40+
if ((multiFactorClass == null) || (authentication == null)) {
41+
return false;
42+
}
43+
44+
return multiFactorClass.isAssignableFrom(authentication.getClass());
45+
}
46+
47+
public void setMultiFactorClass(Class<? extends Authentication> multiFactorClass) {this.multiFactorClass = multiFactorClass; }
48+
49+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
* http://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.GrantedAuthority;
20+
import org.springframework.security.core.SpringSecurityCoreVersion;
21+
22+
import java.util.Collection;
23+
24+
/**
25+
* Represents a principal in the middle of multi factor (step) authentication
26+
*
27+
* @author Yoshikazu Nojima
28+
*/
29+
public class MultiFactorAuthenticationToken extends AbstractAuthenticationToken {
30+
31+
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
32+
33+
private Object principal;
34+
private Object credentials;
35+
36+
// ~ Constructors
37+
// ===================================================================================================
38+
public MultiFactorAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
39+
super(authorities);
40+
this.principal = principal;
41+
this.credentials = credentials;
42+
setAuthenticated(true);
43+
}
44+
45+
// ~ Methods
46+
// ========================================================================================================
47+
48+
@Override
49+
public Object getPrincipal() {
50+
return principal;
51+
}
52+
53+
@Override
54+
public Object getCredentials() {
55+
return credentials;
56+
}
57+
58+
@Override
59+
public void eraseCredentials() {
60+
super.eraseCredentials();
61+
credentials = null;
62+
}
63+
64+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
* http://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.junit.Test;
20+
import org.springframework.security.core.authority.AuthorityUtils;
21+
import org.springframework.security.core.userdetails.MFAUserDetails;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.when;
26+
27+
public class MFATokenEvaluatorImplTests {
28+
29+
// ~ Methods
30+
// ========================================================================================================
31+
@Test
32+
public void testCorrectOperationIsAnonymous() {
33+
MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl();
34+
assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new MultiFactorAuthenticationToken("ignored",
35+
"ignored", AuthorityUtils.createAuthorityList("ignored")))).isTrue();
36+
assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new TestingAuthenticationToken("ignored",
37+
"ignored", AuthorityUtils.createAuthorityList("ignored")))).isFalse();
38+
}
39+
40+
@Test
41+
public void testIsSingleFactorAuthenticationAllowedWithNonMFAUserDetailsPrincipal(){
42+
MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl();
43+
boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken("", ""));
44+
assertThat(result).isFalse();
45+
}
46+
47+
@Test
48+
public void testIsSingleFactorAuthenticationAllowedWithMFAUserDetailsPrincipal(){
49+
MFAUserDetails mfaUserDetails = mock(MFAUserDetails.class);
50+
when(mfaUserDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true);
51+
MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl();
52+
boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken(mfaUserDetails, ""));
53+
assertThat(result).isTrue();
54+
}
55+
56+
@Test
57+
public void testGettersSetters() {
58+
MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl();
59+
60+
assertThat(MultiFactorAuthenticationToken.class).isEqualTo(
61+
mfaTokenEvaluator.getMultiFactorClass());
62+
mfaTokenEvaluator.setMultiFactorClass(TestingAuthenticationToken.class);
63+
assertThat(mfaTokenEvaluator.getMultiFactorClass()).isEqualTo(
64+
TestingAuthenticationToken.class);
65+
66+
assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isTrue();
67+
mfaTokenEvaluator.setSingleFactorAuthenticationAllowed(false);
68+
assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isFalse();
69+
70+
}
71+
72+
}

0 commit comments

Comments
 (0)