From f1ccf2f638552d68a9b81b9d3ad4f8ea851808cc Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 21 Apr 2018 00:54:05 +0900 Subject: [PATCH 1/3] Add MultifactorAuthenticationToken to indicate the state of principal which passed first step of multi step (factor) authentication. --- .../WebSecurityConfigurerAdapter.java | 9 +++ .../ExceptionHandlingConfigurer.java | 18 +++++ .../SessionManagementConfigurer.java | 6 ++ .../AuthenticationTrustResolverImpl.java | 10 +++ .../authentication/MFATokenEvaluator.java | 39 ++++++++++ .../authentication/MFATokenEvaluatorImpl.java | 49 +++++++++++++ .../MultiFactorAuthenticationToken.java | 64 +++++++++++++++++ .../MFATokenEvaluatorImplTests.java | 72 +++++++++++++++++++ .../MultiFactorAuthenticationTokenTests.java | 61 ++++++++++++++++ .../access/ExceptionTranslationFilter.java | 23 +++++- .../HttpSessionSecurityContextRepository.java | 21 +++++- .../ExceptionTranslationFilterTests.java | 35 +++++++++ 12 files changed, 401 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java create mode 100644 core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java create mode 100644 core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index 0377e53a321..77c7bdfeb69 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -40,6 +40,8 @@ import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MFATokenEvaluatorImpl; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; @@ -113,6 +115,7 @@ public T postProcess(T object) { private boolean authenticationManagerInitialized; private AuthenticationManager authenticationManager; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); private HttpSecurity http; private boolean disableDefaults; @@ -391,6 +394,11 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { this.trustResolver = trustResolver; } + @Autowired(required = false) + public void setMfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){ + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + @Autowired(required = false) public void setContentNegotationStrategy( ContentNegotiationStrategy contentNegotiationStrategy) { @@ -420,6 +428,7 @@ private Map, Object> createSharedObjects() { sharedObjects.put(ApplicationContext.class, context); sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy); sharedObjects.put(AuthenticationTrustResolver.class, trustResolver); + sharedObjects.put(MFATokenEvaluator.class, mfaTokenEvaluator); return sharedObjects; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java index 9fa3ad9c470..b462cec06af 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java @@ -17,6 +17,7 @@ import java.util.LinkedHashMap; +import org.springframework.security.authentication.MFATokenEvaluator; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.AuthenticationEntryPoint; @@ -67,6 +68,8 @@ public final class ExceptionHandlingConfigurer> private AuthenticationEntryPoint authenticationEntryPoint; + private MFATokenEvaluator mfaTokenEvaluator; + private AccessDeniedHandler accessDeniedHandler; private LinkedHashMap defaultEntryPointMappings = new LinkedHashMap<>(); @@ -151,6 +154,18 @@ public ExceptionHandlingConfigurer authenticationEntryPoint( return this; } + /** + * Specifies the {@link MFATokenEvaluator} to be used + * + * @param mfaTokenEvaluator the {@link MFATokenEvaluator} to be used + * @return the {@link ExceptionHandlingConfigurer} for further customization + */ + public ExceptionHandlingConfigurer mfaTokenEvaluator( + MFATokenEvaluator mfaTokenEvaluator) { + this.mfaTokenEvaluator = mfaTokenEvaluator; + return this; + } + /** * Sets a default {@link AuthenticationEntryPoint} to be used which prefers being * invoked for the provided {@link RequestMatcher}. If only a single default @@ -194,6 +209,9 @@ public void configure(H http) throws Exception { entryPoint, getRequestCache(http)); AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); + if(mfaTokenEvaluator != null){ + exceptionTranslationFilter.setMFATokenEvaluator(mfaTokenEvaluator); + } exceptionTranslationFilter = postProcess(exceptionTranslationFilter); http.addFilter(exceptionTranslationFilter); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index 43ea86d7f01..b38d832f05e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -26,6 +26,7 @@ import org.springframework.context.event.GenericApplicationListenerAdapter; import org.springframework.context.event.SmartApplicationListener; import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.MFATokenEvaluator; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -425,6 +426,11 @@ public void init(H http) throws Exception { if (trustResolver != null) { httpSecurityRepository.setTrustResolver(trustResolver); } + MFATokenEvaluator mfaTokenEvaluator = http + .getSharedObject(MFATokenEvaluator.class); + if(mfaTokenEvaluator != null){ + httpSecurityRepository.setMFATokenEvaluator(mfaTokenEvaluator); + } http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository); } diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java index 65ee8bafbfd..ba385bd0aef 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java @@ -36,6 +36,8 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol private Class anonymousClass = AnonymousAuthenticationToken.class; private Class rememberMeClass = RememberMeAuthenticationToken.class; + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + // ~ Methods // ======================================================================================================== @@ -52,6 +54,10 @@ public boolean isAnonymous(Authentication authentication) { return false; } + if(mfaTokenEvaluator != null && mfaTokenEvaluator.isMultiFactorAuthentication(authentication)){ + return true; + } + return anonymousClass.isAssignableFrom(authentication.getClass()); } @@ -70,4 +76,8 @@ public void setAnonymousClass(Class anonymousClass) { public void setRememberMeClass(Class rememberMeClass) { this.rememberMeClass = rememberMeClass; } + + public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){ + this.mfaTokenEvaluator = mfaTokenEvaluator; + } } diff --git a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java new file mode 100644 index 00000000000..731acae7105 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.security.core.Authentication; + +/** + * Evaluates Authentication tokens + * + * @author Yoshikazu Nojima + */ +public interface MFATokenEvaluator { + + /** + * Indicates whether the passed Authentication token represents a + * user in the middle of multi factor authentication process. + * + * @param authentication to test (may be null in which case the method + * will always return false) + * + * @return true the passed authentication token represented a principal + * in the middle of multi factor authentication process, false otherwise + */ + boolean isMultiFactorAuthentication(Authentication authentication); +} diff --git a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java new file mode 100644 index 00000000000..48159c81752 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.security.core.Authentication; + +/** + * Basic implementation of {@link MFATokenEvaluator}. + *

+ * Makes trust decisions based on whether the passed Authentication is an + * instance of a defined class. + *

+ * If {@link #multiFactorClass} is null, the + * corresponding method will always return false. + * + * @author Yoshikazu Nojima + */ +public class MFATokenEvaluatorImpl implements MFATokenEvaluator { + + private Class multiFactorClass = MultiFactorAuthenticationToken.class; + + Class getMultiFactorClass() { return multiFactorClass; } + + @Override + public boolean isMultiFactorAuthentication(Authentication authentication) { + if ((multiFactorClass == null) || (authentication == null)) { + return false; + } + + return multiFactorClass.isAssignableFrom(authentication.getClass()); + } + + public void setMultiFactorClass(Class multiFactorClass) {this.multiFactorClass = multiFactorClass; } + +} diff --git a/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java new file mode 100644 index 00000000000..fec06f769b0 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; + +import java.util.Collection; + +/** + * Represents a principal in the middle of multi factor (step) authentication + * + * @author Yoshikazu Nojima + */ +public class MultiFactorAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + private Object principal; + private Object credentials; + + // ~ Constructors + // =================================================================================================== + public MultiFactorAuthenticationToken(Object principal, Object credentials, Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + setAuthenticated(true); + } + + // ~ Methods + // ======================================================================================================== + + @Override + public Object getPrincipal() { + return principal; + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + credentials = null; + } + +} diff --git a/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java b/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java new file mode 100644 index 00000000000..e0b2fa4349b --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.junit.Test; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.MFAUserDetails; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MFATokenEvaluatorImplTests { + + // ~ Methods + // ======================================================================================================== + @Test + public void testCorrectOperationIsAnonymous() { + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new MultiFactorAuthenticationToken("ignored", + "ignored", AuthorityUtils.createAuthorityList("ignored")))).isTrue(); + assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new TestingAuthenticationToken("ignored", + "ignored", AuthorityUtils.createAuthorityList("ignored")))).isFalse(); + } + + @Test + public void testIsSingleFactorAuthenticationAllowedWithNonMFAUserDetailsPrincipal(){ + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken("", "")); + assertThat(result).isFalse(); + } + + @Test + public void testIsSingleFactorAuthenticationAllowedWithMFAUserDetailsPrincipal(){ + MFAUserDetails mfaUserDetails = mock(MFAUserDetails.class); + when(mfaUserDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken(mfaUserDetails, "")); + assertThat(result).isTrue(); + } + + @Test + public void testGettersSetters() { + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + + assertThat(MultiFactorAuthenticationToken.class).isEqualTo( + mfaTokenEvaluator.getMultiFactorClass()); + mfaTokenEvaluator.setMultiFactorClass(TestingAuthenticationToken.class); + assertThat(mfaTokenEvaluator.getMultiFactorClass()).isEqualTo( + TestingAuthenticationToken.class); + + assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isTrue(); + mfaTokenEvaluator.setSingleFactorAuthenticationAllowed(false); + assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isFalse(); + + } + +} diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java new file mode 100644 index 00000000000..bc37182b273 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.junit.Test; +import org.springframework.security.core.authority.AuthorityUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MultiFactorAuthenticationTokenTests { + // ~ Methods + // ======================================================================================================== + + @Test + public void authenticatedPropertyContractIsSatisfied() { + MultiFactorAuthenticationToken token = new MultiFactorAuthenticationToken( + "Test", "Password", AuthorityUtils.NO_AUTHORITIES); + + // check default given we passed some GrantedAuthority[]s (well, we passed empty + // list) + assertThat(token.isAuthenticated()).isTrue(); + + // check explicit set to untrusted (we can safely go from trusted to untrusted, + // but not the reverse) + token.setAuthenticated(false); + assertThat(token.isAuthenticated()).isFalse(); + + } + + @Test + public void gettersReturnCorrectData() { + MultiFactorAuthenticationToken token = new MultiFactorAuthenticationToken( + "Test", "Password", + AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); + assertThat(token.getPrincipal()).isEqualTo("Test"); + assertThat(token.getCredentials()).isEqualTo("Password"); + assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_ONE"); + assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_TWO"); + } + + @Test(expected = NoSuchMethodException.class) + public void testNoArgConstructorDoesntExist() throws Exception { + Class clazz = UsernamePasswordAuthenticationToken.class; + clazz.getDeclaredConstructor((Class[]) null); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java index 5b1010d02c5..8a6be683680 100644 --- a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java @@ -19,6 +19,8 @@ import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MFATokenEvaluatorImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; @@ -82,6 +84,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private RequestCache requestCache = new HttpSessionRequestCache(); @@ -164,6 +167,8 @@ protected AuthenticationTrustResolver getAuthenticationTrustResolver() { return authenticationTrustResolver; } + protected MFATokenEvaluator getMFATokenEvaluator(){ return mfaTokenEvaluator; } + private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { @@ -205,9 +210,15 @@ else if (exception instanceof AccessDeniedException) { protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContextHolder.getContext().setAuthentication(null); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (mfaTokenEvaluator.isMultiFactorAuthentication(authentication)) { + // no-op if in the middle of multi step authentication + } + else { + // SEC-112: Clear the SecurityContextHolder's Authentication, as the + // existing Authentication is no longer considered valid + SecurityContextHolder.getContext().setAuthentication(null); + } requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); authenticationEntryPoint.commence(request, response, reason); @@ -225,6 +236,12 @@ public void setAuthenticationTrustResolver( this.authenticationTrustResolver = authenticationTrustResolver; } + public void setMFATokenEvaluator( + MFATokenEvaluator mfaTokenEvaluator){ + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must not be null"); + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) { Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null"); this.throwableAnalyzer = throwableAnalyzer; diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java index 01f84c3063b..d5b0e890eed 100644 --- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java @@ -29,6 +29,8 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MFATokenEvaluatorImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.Transient; import org.springframework.security.core.context.SecurityContext; @@ -99,6 +101,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); /** * Gets the security context for the current request (if available) and returns it. @@ -335,8 +338,8 @@ final class SaveToSessionResponseWrapper extends /** * Stores the supplied security context in the session (if available) and if it * has changed since it was set at the start of the request. If the - * AuthenticationTrustResolver identifies the current user as anonymous, then the - * context will not be stored. + * AuthenticationTrustResolver identifies the current user as anonymous, but not + * in the middle of multi factor authentication, then the context will not be stored. * * @param context the context object obtained from the SecurityContextHolder after * the request has been processed by the filter chain. @@ -350,7 +353,7 @@ protected void saveContext(SecurityContext context) { HttpSession httpSession = request.getSession(false); // See SEC-776 - if (authentication == null || trustResolver.isAnonymous(authentication)) { + if (authentication == null || trustResolver.isAnonymous(authentication) && !mfaTokenEvaluator.isMultiFactorAuthentication(authentication)) { if (logger.isDebugEnabled()) { logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession."); } @@ -459,4 +462,16 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { Assert.notNull(trustResolver, "trustResolver cannot be null"); this.trustResolver = trustResolver; } + + /** + * Sets the {@link MFATokenEvaluator} to be used. The default is + * {@link MFATokenEvaluatorImpl}. + * + * @param mfaTokenEvaluator the {@link MFATokenEvaluator} to use. Cannot be + * null. + */ + public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator cannot be null"); + this.mfaTokenEvaluator = mfaTokenEvaluator; + } } diff --git a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java index 14d779f2925..3c2db47c7f6 100644 --- a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java @@ -43,6 +43,7 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.MultiFactorAuthenticationToken; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; @@ -112,6 +113,40 @@ public void testAccessDeniedWhenAnonymous() throws Exception { assertThat(getSavedRequestUrl(request)).isEqualTo("http://www.example.com/mycontext/secure/page.html"); } + @Test + public void testAccessDeniedWhenMultiFactorAuthentication() throws Exception { + // Setup our HTTP request + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServletPath("/secure/page.html"); + request.setServerPort(80); + request.setScheme("http"); + request.setServerName("www.example.com"); + request.setContextPath("/mycontext"); + request.setRequestURI("/mycontext/secure/page.html"); + + // Setup the FilterChain to thrown an access denied exception + FilterChain fc = mock(FilterChain.class); + doThrow(new AccessDeniedException("")).when(fc).doFilter( + any(HttpServletRequest.class), any(HttpServletResponse.class)); + + // Setup SecurityContextHolder, as filter needs to check if user is + // anonymous + SecurityContextHolder.getContext().setAuthentication( + new MultiFactorAuthenticationToken("ignored", "ignored", AuthorityUtils + .createAuthorityList("IGNORED"))); + + // Test + ExceptionTranslationFilter filter = new ExceptionTranslationFilter(mockEntryPoint); + filter.setAuthenticationTrustResolver(new AuthenticationTrustResolverImpl()); + assertThat(filter.getAuthenticationTrustResolver()).isNotNull(); + + MockHttpServletResponse response = new MockHttpServletResponse(); + filter.doFilter(request, response, fc); + assertThat(response.getRedirectedUrl()).isEqualTo("/mycontext/login.jsp"); + assertThat(getSavedRequestUrl(request)).isEqualTo("http://www.example.com/mycontext/secure/page.html"); + } + + @Test public void testAccessDeniedWithRememberMe() throws Exception { // Setup our HTTP request From c1fc9aecd84281b42cc19f8277eaf5668f96b74d Mon Sep 17 00:00:00 2001 From: ynojima Date: Sun, 8 Jul 2018 11:15:06 +0900 Subject: [PATCH 2/3] Add MultiFactorAuthenticationProvider --- ...actorAuthenticationProviderConfigurer.java | 60 ++++++++++++ ...AuthenticationProviderConfigurerTests.java | 33 +++++++ .../authentication/MFATokenEvaluator.java | 12 +++ .../authentication/MFATokenEvaluatorImpl.java | 31 +++++- .../MultiFactorAuthenticationProvider.java | 94 +++++++++++++++++++ .../core/userdetails/MFAUserDetails.java | 7 ++ ...ultiFactorAuthenticationProviderTests.java | 70 ++++++++++++++ 7 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java create mode 100644 config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java create mode 100644 core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java create mode 100644 core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java new file mode 100644 index 00000000000..e0f656b989f --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.authentication.configurers.mfa; + +import org.springframework.security.authentication.*; +import org.springframework.security.config.annotation.SecurityBuilder; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; + +/** + * + * @param the type of the {@link SecurityBuilder} + */ +public class MultiFactorAuthenticationProviderConfigurer> + extends SecurityConfigurerAdapter { + + //~ Instance fields + // ================================================================================================ + private AuthenticationProvider authenticationProvider; + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + + /** + * Constructor + * @param authenticationProvider {@link AuthenticationProvider} to be delegated + */ + public MultiFactorAuthenticationProviderConfigurer(AuthenticationProvider authenticationProvider) { + this.authenticationProvider = authenticationProvider; + } + + + public static MultiFactorAuthenticationProviderConfigurer multiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider){ + return new MultiFactorAuthenticationProviderConfigurer(authenticationProvider); + } + + @Override + public void configure(B builder) { + MultiFactorAuthenticationProvider multiFactorAuthenticationProvider = new MultiFactorAuthenticationProvider(authenticationProvider, mfaTokenEvaluator); + multiFactorAuthenticationProvider = postProcess(multiFactorAuthenticationProvider); + builder.authenticationProvider(multiFactorAuthenticationProvider); + } + + public MultiFactorAuthenticationProviderConfigurer mfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { + this.mfaTokenEvaluator = mfaTokenEvaluator; + return this; + } +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java new file mode 100644 index 00000000000..5b42dde9db1 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java @@ -0,0 +1,33 @@ +package org.springframework.security.config.annotation.authentication.configurers.mfa; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MultiFactorAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.springframework.security.config.annotation.authentication.configurers.mfa.MultiFactorAuthenticationProviderConfigurer.multiFactorAuthenticationProvider; + +public class MultiFactorAuthenticationProviderConfigurerTests { + + @Test + public void test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + MFATokenEvaluator mfaTokenEvaluator = mock(MFATokenEvaluator.class); + MultiFactorAuthenticationProviderConfigurer configurer + = multiFactorAuthenticationProvider(delegatedAuthenticationProvider); + configurer.mfaTokenEvaluator(mfaTokenEvaluator); + ProviderManagerBuilder providerManagerBuilder = mock(ProviderManagerBuilder.class); + configurer.configure(providerManagerBuilder); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AuthenticationProvider.class); + verify(providerManagerBuilder).authenticationProvider(argumentCaptor.capture()); + MultiFactorAuthenticationProvider authenticationProvider = (MultiFactorAuthenticationProvider)argumentCaptor.getValue(); + + assertThat(authenticationProvider.getAuthenticationProvider()).isEqualTo(delegatedAuthenticationProvider); + assertThat(authenticationProvider.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator); + } +} diff --git a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java index 731acae7105..46a2abe590c 100644 --- a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java +++ b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java @@ -36,4 +36,16 @@ public interface MFATokenEvaluator { * in the middle of multi factor authentication process, false otherwise */ boolean isMultiFactorAuthentication(Authentication authentication); + + /** + * Indicates whether the principal associated with the Authentication + * token is allowed to login with only single factor. + * + * @param authentication to test (may be null in which case the method + * will always return false) + * + * @return true the principal associated with thepassed authentication + * token is allowed to login with only single factor, false otherwise + */ + boolean isSingleFactorAuthenticationAllowed(Authentication authentication); } diff --git a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java index 48159c81752..01a2c9b679a 100644 --- a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java +++ b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java @@ -17,6 +17,7 @@ package org.springframework.security.authentication; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.MFAUserDetails; /** * Basic implementation of {@link MFATokenEvaluator}. @@ -32,8 +33,7 @@ public class MFATokenEvaluatorImpl implements MFATokenEvaluator { private Class multiFactorClass = MultiFactorAuthenticationToken.class; - - Class getMultiFactorClass() { return multiFactorClass; } + private boolean singleFactorAuthenticationAllowed = true; @Override public boolean isMultiFactorAuthentication(Authentication authentication) { @@ -44,6 +44,33 @@ public boolean isMultiFactorAuthentication(Authentication authentication) { return multiFactorClass.isAssignableFrom(authentication.getClass()); } + @Override + public boolean isSingleFactorAuthenticationAllowed(Authentication authentication) { + if(singleFactorAuthenticationAllowed && authentication.getPrincipal() instanceof MFAUserDetails){ + MFAUserDetails webAuthnUserDetails = (MFAUserDetails) authentication.getPrincipal(); + return webAuthnUserDetails.isSingleFactorAuthenticationAllowed(); + } + return false; + } + + Class getMultiFactorClass() { return multiFactorClass; } + public void setMultiFactorClass(Class multiFactorClass) {this.multiFactorClass = multiFactorClass; } + /** + * Check if single factor authentication is allowed + * @return true if single factor authentication is allowed + */ + public boolean isSingleFactorAuthenticationAllowed() { + return singleFactorAuthenticationAllowed; + } + + /** + * Set single factor authentication is allowed + * @param singleFactorAuthenticationAllowed true if single factor authentication is allowed + */ + public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed) { + this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; + } + } diff --git a/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java new file mode 100644 index 00000000000..15ccbcd033d --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.util.Assert; + +import java.util.Collections; + +/** + * An {@link AuthenticationProvider} implementation for the first factor(step) of multi factor authentication. + * Authentication itself is delegated to another {@link AuthenticationProvider}. + */ +public class MultiFactorAuthenticationProvider implements AuthenticationProvider { + + + // ~ Instance fields + // ================================================================================================ + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + + /** + * {@link AuthenticationProvider} to be delegated + */ + private AuthenticationProvider authenticationProvider; + private MFATokenEvaluator mfaTokenEvaluator; + + /** + * Constructor + * @param authenticationProvider {@link AuthenticationProvider} to be delegated + */ + public MultiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider, MFATokenEvaluator mfaTokenEvaluator) { + Assert.notNull(authenticationProvider, "authenticationProvider must be set"); + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must be set"); + this.authenticationProvider = authenticationProvider; + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + + /** + * {@inheritDoc} + */ + @Override + public Authentication authenticate(Authentication authentication) { + if (!supports(authentication.getClass())) { + throw new IllegalArgumentException("Not supported AuthenticationToken " + authentication.getClass() + " was attempted"); + } + + Authentication result = authenticationProvider.authenticate(authentication); + + if(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(result)){ + return result; + } + + return new MultiFactorAuthenticationToken( + result.getPrincipal(), + result.getCredentials(), + Collections.emptyList() // result.getAuthorities() is not used as not to inherit authorities from result + ); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean supports(Class authentication) { + return authenticationProvider.supports(authentication); + } + + /** + * {@link AuthenticationProvider} to be delegated + */ + public AuthenticationProvider getAuthenticationProvider() { + return authenticationProvider; + } + + public MFATokenEvaluator getMFATokenEvaluator() { + return mfaTokenEvaluator; + } +} diff --git a/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java b/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java new file mode 100644 index 00000000000..dd4895c4a68 --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java @@ -0,0 +1,7 @@ +package org.springframework.security.core.userdetails; + +public interface MFAUserDetails extends UserDetails { + + boolean isSingleFactorAuthenticationAllowed(); + +} diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java new file mode 100644 index 00000000000..9b137d807b2 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java @@ -0,0 +1,70 @@ +package org.springframework.security.authentication; + + +import org.junit.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.MFAUserDetails; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MultiFactorAuthenticationProviderTests { + + @Test + public void authenticate_with_singleFactorAuthenticationAllowedOption_false_test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + MFAUserDetails userDetails = mock(MFAUserDetails.class); + when(userDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList()); + authenticationToken.setDetails(userDetails); + when(delegatedAuthenticationProvider.supports(any())).thenReturn(true); + when(delegatedAuthenticationProvider.authenticate(any())) + .thenReturn(new UsernamePasswordAuthenticationToken( + "principal", + "credentials", + Collections.singletonList(new SimpleGrantedAuthority("ROLE_DUMMY")) + )); + + MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); + Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("dummy", "dummy")); + + assertThat(result).isInstanceOf(MultiFactorAuthenticationToken.class); + assertThat(result.getPrincipal()).isEqualTo("principal"); + assertThat(result.getCredentials()).isEqualTo("credentials"); + assertThat(result.getAuthorities()).isEmpty(); + + } + + @Test + public void authenticate_with_singleFactorAuthenticationAllowedOption_true_test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + MFAUserDetails userDetails = mock(MFAUserDetails.class); + when(userDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList()); + authenticationToken.setDetails(userDetails); + when(delegatedAuthenticationProvider.supports(any())).thenReturn(true); + when(delegatedAuthenticationProvider.authenticate(any())) + .thenReturn(authenticationToken); + + MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); + Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("dummy", "dummy")); + + assertThat(result).isInstanceOf(UsernamePasswordAuthenticationToken.class); + assertThat(result).isEqualTo(result); + } + + @Test(expected = IllegalArgumentException.class) + public void authenticate_with_invalid_AuthenticationToken_test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + when(delegatedAuthenticationProvider.supports(any())).thenReturn(false); + + MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); + provider.authenticate(new TestingAuthenticationToken("dummy", "dummy")); + } + +} From d1bfb461147f112da9a8a736f2e8ff697283c0cd Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Wed, 15 Aug 2018 02:13:59 +0900 Subject: [PATCH 3/3] Address checkstyle warnings --- ...actorAuthenticationProviderConfigurer.java | 50 +++++------ ...AuthenticationProviderConfigurerTests.java | 18 +++- .../MultiFactorAuthenticationProvider.java | 84 +++++++++---------- .../core/userdetails/MFAUserDetails.java | 16 ++++ ...ultiFactorAuthenticationProviderTests.java | 16 ++++ 5 files changed, 116 insertions(+), 68 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java index e0f656b989f..5f5d3240a8b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java @@ -26,35 +26,35 @@ * @param the type of the {@link SecurityBuilder} */ public class MultiFactorAuthenticationProviderConfigurer> - extends SecurityConfigurerAdapter { - - //~ Instance fields - // ================================================================================================ - private AuthenticationProvider authenticationProvider; - private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - - /** - * Constructor - * @param authenticationProvider {@link AuthenticationProvider} to be delegated - */ - public MultiFactorAuthenticationProviderConfigurer(AuthenticationProvider authenticationProvider) { - this.authenticationProvider = authenticationProvider; - } + extends SecurityConfigurerAdapter { + + //~ Instance fields + // ================================================================================================ + private AuthenticationProvider authenticationProvider; + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + + /** + * Constructor + * @param authenticationProvider {@link AuthenticationProvider} to be delegated + */ + public MultiFactorAuthenticationProviderConfigurer(AuthenticationProvider authenticationProvider) { + this.authenticationProvider = authenticationProvider; + } public static MultiFactorAuthenticationProviderConfigurer multiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider){ return new MultiFactorAuthenticationProviderConfigurer(authenticationProvider); } - @Override - public void configure(B builder) { - MultiFactorAuthenticationProvider multiFactorAuthenticationProvider = new MultiFactorAuthenticationProvider(authenticationProvider, mfaTokenEvaluator); - multiFactorAuthenticationProvider = postProcess(multiFactorAuthenticationProvider); - builder.authenticationProvider(multiFactorAuthenticationProvider); - } - - public MultiFactorAuthenticationProviderConfigurer mfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { - this.mfaTokenEvaluator = mfaTokenEvaluator; - return this; - } + @Override + public void configure(B builder) { + MultiFactorAuthenticationProvider multiFactorAuthenticationProvider = new MultiFactorAuthenticationProvider(authenticationProvider, mfaTokenEvaluator); + multiFactorAuthenticationProvider = postProcess(multiFactorAuthenticationProvider); + builder.authenticationProvider(multiFactorAuthenticationProvider); + } + + public MultiFactorAuthenticationProviderConfigurer mfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { + this.mfaTokenEvaluator = mfaTokenEvaluator; + return this; + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java index 5b42dde9db1..366de8dfa8c 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.security.config.annotation.authentication.configurers.mfa; import org.junit.Test; @@ -25,7 +41,7 @@ public void test(){ configurer.configure(providerManagerBuilder); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AuthenticationProvider.class); verify(providerManagerBuilder).authenticationProvider(argumentCaptor.capture()); - MultiFactorAuthenticationProvider authenticationProvider = (MultiFactorAuthenticationProvider)argumentCaptor.getValue(); + MultiFactorAuthenticationProvider authenticationProvider = (MultiFactorAuthenticationProvider) argumentCaptor.getValue(); assertThat(authenticationProvider.getAuthenticationProvider()).isEqualTo(delegatedAuthenticationProvider); assertThat(authenticationProvider.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator); diff --git a/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java index 15ccbcd033d..b445057faf2 100644 --- a/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java @@ -30,56 +30,56 @@ public class MultiFactorAuthenticationProvider implements AuthenticationProvider { - // ~ Instance fields - // ================================================================================================ - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + // ~ Instance fields + // ================================================================================================ + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - /** - * {@link AuthenticationProvider} to be delegated - */ - private AuthenticationProvider authenticationProvider; - private MFATokenEvaluator mfaTokenEvaluator; + /** + * {@link AuthenticationProvider} to be delegated + */ + private AuthenticationProvider authenticationProvider; + private MFATokenEvaluator mfaTokenEvaluator; - /** - * Constructor - * @param authenticationProvider {@link AuthenticationProvider} to be delegated - */ - public MultiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider, MFATokenEvaluator mfaTokenEvaluator) { - Assert.notNull(authenticationProvider, "authenticationProvider must be set"); + /** + * Constructor + * @param authenticationProvider {@link AuthenticationProvider} to be delegated + */ + public MultiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider, MFATokenEvaluator mfaTokenEvaluator) { + Assert.notNull(authenticationProvider, "authenticationProvider must be set"); Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must be set"); - this.authenticationProvider = authenticationProvider; - this.mfaTokenEvaluator = mfaTokenEvaluator; - } + this.authenticationProvider = authenticationProvider; + this.mfaTokenEvaluator = mfaTokenEvaluator; + } - /** - * {@inheritDoc} - */ - @Override - public Authentication authenticate(Authentication authentication) { - if (!supports(authentication.getClass())) { - throw new IllegalArgumentException("Not supported AuthenticationToken " + authentication.getClass() + " was attempted"); - } + /** + * {@inheritDoc} + */ + @Override + public Authentication authenticate(Authentication authentication) { + if (!supports(authentication.getClass())) { + throw new IllegalArgumentException("Not supported AuthenticationToken " + authentication.getClass() + " was attempted"); + } - Authentication result = authenticationProvider.authenticate(authentication); + Authentication result = authenticationProvider.authenticate(authentication); - if(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(result)){ - return result; - } + if(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(result)){ + return result; + } - return new MultiFactorAuthenticationToken( - result.getPrincipal(), - result.getCredentials(), - Collections.emptyList() // result.getAuthorities() is not used as not to inherit authorities from result - ); - } + return new MultiFactorAuthenticationToken( + result.getPrincipal(), + result.getCredentials(), + Collections.emptyList() // result.getAuthorities() is not used as not to inherit authorities from result + ); + } - /** - * {@inheritDoc} - */ - @Override - public boolean supports(Class authentication) { - return authenticationProvider.supports(authentication); - } + /** + * {@inheritDoc} + */ + @Override + public boolean supports(Class authentication) { + return authenticationProvider.supports(authentication); + } /** * {@link AuthenticationProvider} to be delegated diff --git a/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java b/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java index dd4895c4a68..a0174e2ad68 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.security.core.userdetails; public interface MFAUserDetails extends UserDetails { diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java index 9b137d807b2..c8b11d3c0f5 100644 --- a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.security.authentication;