From 9642d33a6b2847291cd2a9b9357add698cb8f34a Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 4 Feb 2019 17:33:44 -0700 Subject: [PATCH] NamespaceHttpX509Tests groovy->java Fixes: gh-4939 --- .../configurers/NamespaceHttpX509Tests.groovy | 289 ------------------ .../configurers/NamespaceHttpX509Tests.java | 280 +++++++++++++++++ 2 files changed, 280 insertions(+), 289 deletions(-) delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.groovy create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.groovy deleted file mode 100644 index 7e80ea20cc..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.groovy +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2002-2013 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.web.configurers - -import sun.security.x509.X500Name - -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate - -import javax.servlet.http.HttpServletRequest - -import org.springframework.context.annotation.Configuration -import org.springframework.mock.web.MockFilterChain -import org.springframework.mock.web.MockHttpServletRequest -import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.security.authentication.AuthenticationDetailsSource -import org.springframework.security.authentication.AuthenticationManager -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.authority.AuthorityUtils -import org.springframework.security.core.userdetails.AuthenticationUserDetailsService -import org.springframework.security.core.userdetails.User -import org.springframework.security.core.userdetails.UserDetails -import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.security.web.FilterChainProxy -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken -import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails -import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter -import org.springframework.security.web.context.HttpRequestResponseHolder -import org.springframework.security.web.context.HttpSessionSecurityContextRepository -import org.springframework.test.util.ReflectionTestUtils - -/** - * Tests to verify that all the functionality of attributes is present - * - * @author Rob Winch - * - */ -public class NamespaceHttpX509Tests extends BaseSpringSpec { - def "http/x509 can authenticate"() { - setup: - X509Certificate certificate = loadCert("rod.cer") - loadConfig(X509Config) - when: - request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) - springSecurityFilterChain.doFilter(request, response, chain); - then: - response.status == 200 - authentication().name == 'rod' - } - - def "http/x509"() { - when: - loadConfig(X509Config) - X509AuthenticationFilter filter = findFilter(X509AuthenticationFilter) - AuthenticationManager authenticationManager = ReflectionTestUtils.getField(filter,"authenticationManager") - then: - authenticationManager - filter.authenticationDetailsSource.class == WebAuthenticationDetailsSource - authenticationManager.providers.find { it instanceof PreAuthenticatedAuthenticationProvider }.preAuthenticatedUserDetailsService.class == UserDetailsByNameServiceWrapper - } - - @EnableWebSecurity - public static class X509Config extends WebSecurityConfigurerAdapter { - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth. - inMemoryAuthentication() - .withUser("rod").password("password").roles("USER","ADMIN"); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .x509(); - } - } - - def "http/x509@authentication-details-source-ref"() { - setup: - AuthenticationDetailsSourceRefConfig.AUTHENTICATION_DETAILS_SOURCE = Mock(AuthenticationDetailsSource) - when: - loadConfig(AuthenticationDetailsSourceRefConfig) - X509AuthenticationFilter filter = findFilter(X509AuthenticationFilter) - AuthenticationManager authenticationManager = ReflectionTestUtils.getField(filter,"authenticationManager") - then: - authenticationManager - filter.authenticationDetailsSource == AuthenticationDetailsSourceRefConfig.AUTHENTICATION_DETAILS_SOURCE - authenticationManager.providers.find { it instanceof PreAuthenticatedAuthenticationProvider }.preAuthenticatedUserDetailsService.class == UserDetailsByNameServiceWrapper - } - - @EnableWebSecurity - public static class AuthenticationDetailsSourceRefConfig extends WebSecurityConfigurerAdapter { - static AuthenticationDetailsSource AUTHENTICATION_DETAILS_SOURCE - - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth. - inMemoryAuthentication() - .withUser("rod").password("password").roles("USER","ADMIN"); - } - - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .x509() - .authenticationDetailsSource(AUTHENTICATION_DETAILS_SOURCE); - } - } - - def "http/x509@subject-principal-regex"() { - setup: - X509Certificate certificate = loadCert("rodatexampledotcom.cer") - loadConfig(SubjectPrincipalRegexConfig) - when: - request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) - springSecurityFilterChain.doFilter(request, response, chain); - then: - response.status == 200 - authentication().name == 'rod' - } - - @EnableWebSecurity - public static class SubjectPrincipalRegexConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth. - inMemoryAuthentication() - .withUser("rod").password("password").roles("USER","ADMIN"); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .x509() - .subjectPrincipalRegex('CN=(.*?)@example.com(?:,|$)'); - } - } - - def "http/x509@custom-principal-extractor"() { - setup: - X509Certificate certificate = loadCert("rodatexampledotcom.cer") - loadConfig(CustomPrincipalExtractorConfig) - when: - request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) - springSecurityFilterChain.doFilter(request, response, chain) - then: - response.status == 200 - authentication().name == 'rod@example.com' - } - - @EnableWebSecurity - public static class CustomPrincipalExtractorConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth. - inMemoryAuthentication() - .withUser("rod@example.com").password("password").roles("USER","ADMIN"); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .x509() - .x509PrincipalExtractor{ (it.subjectDN as X500Name).commonName } - } - } - - def "http/x509@user-service-ref"() { - setup: - X509Certificate certificate = loadCert("rodatexampledotcom.cer") - loadConfig(UserDetailsServiceRefConfig) - when: - request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) - springSecurityFilterChain.doFilter(request, response, chain); - then: - response.status == 200 - authentication().name == 'customuser' - } - - @EnableWebSecurity - public static class UserDetailsServiceRefConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth. - inMemoryAuthentication() - .withUser("rod").password("password").roles("USER","ADMIN"); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .x509() - .userDetailsService(new CustomUserDetailsService()); - } - } - - def "http/x509 custom AuthenticationUserDetailsService"() { - setup: - X509Certificate certificate = loadCert("rodatexampledotcom.cer") - loadConfig(AuthenticationUserDetailsServiceConfig) - when: - request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) - springSecurityFilterChain.doFilter(request, response, chain); - then: - response.status == 200 - authentication().name == 'customuser' - } - - @EnableWebSecurity - public static class AuthenticationUserDetailsServiceConfig extends WebSecurityConfigurerAdapter { - static AuthenticationDetailsSource AUTHENTICATION_DETAILS_SOURCE - - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth. - inMemoryAuthentication() - .withUser("rod").password("password").roles("USER","ADMIN"); - } - - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .x509() - .userDetailsService(new CustomUserDetailsService()); - } - } - - def loadCert(String location) { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - certFactory.generateCertificate(Thread.currentThread().contextClassLoader.getResourceAsStream(location)) - } - - static class CustomUserDetailsService implements UserDetailsService { - - public UserDetails loadUserByUsername(String username) - throws UsernameNotFoundException { - return new User("customuser", "password", AuthorityUtils.createAuthorityList("ROLE_USER")); - } - - } - - static class CustomAuthenticationUserDetailsService implements AuthenticationUserDetailsService { - public UserDetails loadUserDetails( - PreAuthenticatedAuthenticationToken token) - throws UsernameNotFoundException { - return new User("customuser", "password", AuthorityUtils.createAuthorityList("ROLE_USER")); - } - } - - def authentication() { - HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response) - new HttpSessionSecurityContextRepository().loadContext(requestResponseHolder)?.authentication - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java new file mode 100644 index 0000000000..64c91dc3ed --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java @@ -0,0 +1,280 @@ +/* + * Copyright 2002-2019 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.web.configurers; + +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import javax.servlet.http.HttpServletRequest; + +import org.junit.Rule; +import org.junit.Test; +import sun.security.x509.X500Name; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; + +/** + * Tests to verify that all the functionality of attributes is present in Java config + * + * @author Rob Winch + * @author Josh Cummings + * + */ +public class NamespaceHttpX509Tests { + + private static final User USER = + new User("customuser", "password", AuthorityUtils.createAuthorityList("ROLE_USER")); + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void x509AuthenticationWhenUsingX509DefaultConfigurationThenMatchesNamespace() throws Exception { + this.spring.register(X509Config.class, X509Controller.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + this.mvc.perform(get("/whoami").with(x509(certificate))) + .andExpect(content().string("rod")); + } + + @EnableWebSecurity + @EnableWebMvc + public static class X509Config extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .x509(); + } + } + + @Test + public void x509AuthenticationWhenHasCustomAuthenticationDetailsSourceThenMatchesNamespace() throws Exception { + this.spring.register(AuthenticationDetailsSourceRefConfig.class, X509Controller.class).autowire(); + + X509Certificate certificate = loadCert("rod.cer"); + this.mvc.perform(get("/whoami").with(x509(certificate))) + .andExpect(content().string("rod")); + + verifyBean(AuthenticationDetailsSource.class).buildDetails(any()); + } + + @EnableWebSecurity + @EnableWebMvc + static class AuthenticationDetailsSourceRefConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .x509() + .authenticationDetailsSource(authenticationDetailsSource()); + } + + @Bean + AuthenticationDetailsSource + authenticationDetailsSource() { + + return mock(AuthenticationDetailsSource.class); + } + } + + @Test + public void x509AuthenticationWhenHasSubjectPrincipalRegexThenMatchesNamespace() throws Exception { + this.spring.register(SubjectPrincipalRegexConfig.class, X509Controller.class).autowire(); + X509Certificate certificate = loadCert("rodatexampledotcom.cer"); + this.mvc.perform(get("/whoami").with(x509(certificate))) + .andExpect(content().string("rod")); + } + + @EnableWebMvc + @EnableWebSecurity + public static class SubjectPrincipalRegexConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .x509() + .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)"); + } + } + + @Test + public void x509AuthenticationWhenHasCustomPrincipalExtractorThenMatchesNamespace() throws Exception { + this.spring.register(CustomPrincipalExtractorConfig.class, X509Controller.class).autowire(); + X509Certificate certificate = loadCert("rodatexampledotcom.cer"); + this.mvc.perform(get("/whoami").with(x509(certificate))) + .andExpect(content().string("rod@example.com")); + } + + @EnableWebMvc + @EnableWebSecurity + public static class CustomPrincipalExtractorConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("rod@example.com").password("password").roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .x509() + .x509PrincipalExtractor(this::extractCommonName); + } + + private String extractCommonName(X509Certificate certificate) { + try { + return ((X500Name) certificate.getSubjectDN()).getCommonName(); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + } + + @Test + public void x509AuthenticationWhenHasCustomUserDetailsServiceThenMatchesNamespace() throws Exception { + this.spring.register(UserDetailsServiceRefConfig.class, X509Controller.class).autowire(); + X509Certificate certificate = loadCert("rodatexampledotcom.cer"); + this.mvc.perform(get("/whoami").with(x509(certificate))) + .andExpect(content().string("customuser")); + } + + @EnableWebMvc + @EnableWebSecurity + public static class UserDetailsServiceRefConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .x509() + .userDetailsService(username -> USER); + } + } + + @Test + public void x509AuthenticationWhenHasCustomAuthenticationUserDetailsServiceThenMatchesNamespace() throws Exception { + this.spring.register(AuthenticationUserDetailsServiceConfig.class, X509Controller.class).autowire(); + X509Certificate certificate = loadCert("rodatexampledotcom.cer"); + this.mvc.perform(get("/whoami").with(x509(certificate))) + .andExpect(content().string("customuser")); + } + + @EnableWebMvc + @EnableWebSecurity + public static class AuthenticationUserDetailsServiceConfig extends WebSecurityConfigurerAdapter { + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth. + inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + } + + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .x509() + .authenticationUserDetailsService(authentication -> USER); + } + } + + @RestController + public static class X509Controller { + @GetMapping("/whoami") + public String whoami(@AuthenticationPrincipal(expression="username") String name) { + return name; + } + } + + T loadCert(String location) { + try (InputStream is = new ClassPathResource(location).getInputStream()) { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (T) certFactory.generateCertificate(is); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + T verifyBean(Class beanClass) { + return verify(this.spring.getContext().getBean(beanClass)); + } +}