25 changed files with 1307 additions and 4 deletions
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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 org.springframework.security.config.annotation.web.HttpSecurityBuilder; |
||||
import org.springframework.security.web.RequestMatcherRedirectFilter; |
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Adds password management support. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @since 5.6 |
||||
*/ |
||||
public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>> |
||||
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> { |
||||
|
||||
private static final String WELL_KNOWN_CHANGE_PASSWORD_PATTERN = "/.well-known/change-password"; |
||||
|
||||
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = "/change-password"; |
||||
|
||||
private String changePasswordPage = DEFAULT_CHANGE_PASSWORD_PAGE; |
||||
|
||||
/** |
||||
* Sets the change password page. Defaults to |
||||
* {@link PasswordManagementConfigurer#DEFAULT_CHANGE_PASSWORD_PAGE}. |
||||
* @param changePasswordPage the change password page |
||||
* @return the {@link PasswordManagementConfigurer} for further customizations |
||||
*/ |
||||
public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordPage) { |
||||
Assert.hasText(changePasswordPage, "changePasswordPage cannot be empty"); |
||||
this.changePasswordPage = changePasswordPage; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public void configure(B http) throws Exception { |
||||
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter( |
||||
new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage); |
||||
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.http; |
||||
|
||||
import org.w3c.dom.Element; |
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser; |
||||
import org.springframework.beans.factory.xml.ParserContext; |
||||
import org.springframework.security.web.RequestMatcherRedirectFilter; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* The bean definition parser for a Well-Known URL for Changing Passwords. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @since 5.6 |
||||
*/ |
||||
public final class WellKnownChangePasswordBeanDefinitionParser implements BeanDefinitionParser { |
||||
|
||||
private static final String WELL_KNOWN_CHANGE_PASSWORD_PATTERN = "/.well-known/change-password"; |
||||
|
||||
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = "/change-password"; |
||||
|
||||
private static final String ATT_CHANGE_PASSWORD_PAGE = "change-password-page"; |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public BeanDefinition parse(Element element, ParserContext parserContext) { |
||||
BeanDefinition changePasswordFilter = BeanDefinitionBuilder |
||||
.rootBeanDefinition(RequestMatcherRedirectFilter.class) |
||||
.addConstructorArgValue(new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN)) |
||||
.addConstructorArgValue(getChangePasswordPage(element)).getBeanDefinition(); |
||||
parserContext.getReaderContext().registerWithGeneratedName(changePasswordFilter); |
||||
return changePasswordFilter; |
||||
} |
||||
|
||||
private String getChangePasswordPage(Element element) { |
||||
String changePasswordPage = element.getAttribute(ATT_CHANGE_PASSWORD_PAGE); |
||||
return (StringUtils.hasText(changePasswordPage) ? changePasswordPage : DEFAULT_CHANGE_PASSWORD_PAGE); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.server |
||||
|
||||
/** |
||||
* A Kotlin DSL to configure [ServerHttpSecurity] password management |
||||
* using idiomatic Kotlin code. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @property changePasswordPage the change password page. |
||||
* @since 5.6 |
||||
*/ |
||||
@ServerSecurityMarker |
||||
class ServerPasswordManagementDsl { |
||||
var changePasswordPage: String? = null |
||||
|
||||
internal fun get(): (ServerHttpSecurity.PasswordManagementSpec) -> Unit { |
||||
return { passwordManagement -> |
||||
changePasswordPage?.also { passwordManagement.changePasswordPage(changePasswordPage) } |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.servlet |
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity |
||||
import org.springframework.security.config.annotation.web.configurers.PasswordManagementConfigurer |
||||
|
||||
/** |
||||
* A Kotlin DSL to configure [HttpSecurity] password management |
||||
* using idiomatic Kotlin code. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @property changePasswordPage the change password page. |
||||
* @since 5.6 |
||||
*/ |
||||
@SecurityMarker |
||||
class PasswordManagementDsl { |
||||
var changePasswordPage: String? = null |
||||
|
||||
internal fun get(): (PasswordManagementConfigurer<HttpSecurity>) -> Unit { |
||||
return { passwordManagement -> |
||||
changePasswordPage?.also { passwordManagement.changePasswordPage(changePasswordPage) } |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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 org.junit.Rule; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||
import org.springframework.security.config.test.SpringTestRule; |
||||
import org.springframework.security.web.SecurityFilterChain; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.springframework.security.config.Customizer.withDefaults; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
||||
/** |
||||
* Tests for {@link PasswordManagementConfigurer}. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
public class PasswordManagementConfigurerTests { |
||||
|
||||
@Rule |
||||
public final SpringTestRule spring = new SpringTestRule(); |
||||
|
||||
@Autowired |
||||
MockMvc mvc; |
||||
|
||||
@Test |
||||
public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() throws Exception { |
||||
this.spring.register(PasswordManagementWithDefaultChangePasswordPageConfig.class).autowire(); |
||||
|
||||
this.mvc.perform(get("/.well-known/change-password")).andExpect(status().isFound()) |
||||
.andExpect(redirectedUrl("/change-password")); |
||||
} |
||||
|
||||
@Test |
||||
public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() throws Exception { |
||||
this.spring.register(PasswordManagementWithCustomChangePasswordPageConfig.class).autowire(); |
||||
|
||||
this.mvc.perform(get("/.well-known/change-password")).andExpect(status().isFound()) |
||||
.andExpect(redirectedUrl("/custom-change-password-page")); |
||||
} |
||||
|
||||
@Test |
||||
public void whenSettingNullChangePasswordPage() { |
||||
PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage(null)) |
||||
.withMessage("changePasswordPage cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void whenSettingEmptyChangePasswordPage() { |
||||
PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage("")) |
||||
.withMessage("changePasswordPage cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void whenSettingBlankChangePasswordPage() { |
||||
PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage(" ")) |
||||
.withMessage("changePasswordPage cannot be empty"); |
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class PasswordManagementWithDefaultChangePasswordPageConfig { |
||||
|
||||
@Bean |
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||
// @formatter:off
|
||||
return http |
||||
.passwordManagement(withDefaults()) |
||||
.build(); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class PasswordManagementWithCustomChangePasswordPageConfig { |
||||
|
||||
@Bean |
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||
// @formatter:off
|
||||
return http |
||||
.passwordManagement((passwordManagement) -> passwordManagement |
||||
.changePasswordPage("/custom-change-password-page") |
||||
) |
||||
.build(); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.http; |
||||
|
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.config.test.SpringTestRule; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
||||
/** |
||||
* Tests for {@link WellKnownChangePasswordBeanDefinitionParser}. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
public class WellKnownChangePasswordBeanDefinitionParserTests { |
||||
|
||||
private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests"; |
||||
|
||||
@Rule |
||||
public final SpringTestRule spring = new SpringTestRule(); |
||||
|
||||
@Autowired |
||||
MockMvc mvc; |
||||
|
||||
@Test |
||||
public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() throws Exception { |
||||
this.spring.configLocations(xml("DefaultChangePasswordPage")).autowire(); |
||||
|
||||
this.mvc.perform(get("/.well-known/change-password")).andExpect(status().isFound()) |
||||
.andExpect(redirectedUrl("/change-password")); |
||||
} |
||||
|
||||
@Test |
||||
public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() throws Exception { |
||||
this.spring.configLocations(xml("CustomChangePasswordPage")).autowire(); |
||||
|
||||
this.mvc.perform(get("/.well-known/change-password")).andExpect(status().isFound()) |
||||
.andExpect(redirectedUrl("/custom-change-password-page")); |
||||
} |
||||
|
||||
private String xml(String configName) { |
||||
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.server; |
||||
|
||||
import org.apache.http.HttpHeaders; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; |
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.PasswordManagementSpec; |
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; |
||||
import org.springframework.test.web.reactive.server.WebTestClient; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link PasswordManagementSpec}. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
public class PasswordManagementSpecTests { |
||||
|
||||
ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); |
||||
|
||||
@Test |
||||
public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() { |
||||
this.http.passwordManagement(); |
||||
|
||||
WebTestClient client = buildClient(); |
||||
client.get().uri("/.well-known/change-password").exchange().expectStatus().isFound().expectHeader() |
||||
.valueEquals(HttpHeaders.LOCATION, "/change-password"); |
||||
} |
||||
|
||||
@Test |
||||
public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() { |
||||
this.http.passwordManagement( |
||||
(passwordManagement) -> passwordManagement.changePasswordPage("/custom-change-password-page")); |
||||
|
||||
WebTestClient client = buildClient(); |
||||
client.get().uri("/.well-known/change-password").exchange().expectStatus().isFound().expectHeader() |
||||
.valueEquals(HttpHeaders.LOCATION, "/custom-change-password-page"); |
||||
} |
||||
|
||||
private WebTestClient buildClient() { |
||||
return WebTestClientBuilder.bindToWebFilters(this.http.build()).build(); |
||||
} |
||||
|
||||
@Test |
||||
public void whenSettingNullChangePasswordPage() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.http.passwordManagement().changePasswordPage(null)) |
||||
.withMessage("changePasswordPage cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void whenSettingEmptyChangePasswordPage() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.http.passwordManagement().changePasswordPage("")) |
||||
.withMessage("changePasswordPage cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void whenSettingBlankChangePasswordPage() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.http.passwordManagement().changePasswordPage(" ")) |
||||
.withMessage("changePasswordPage cannot be empty"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.server |
||||
|
||||
import org.apache.http.HttpHeaders |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.springframework.beans.factory.annotation.Autowired |
||||
import org.springframework.context.ApplicationContext |
||||
import org.springframework.context.annotation.Bean |
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity |
||||
import org.springframework.security.config.test.SpringTestRule |
||||
import org.springframework.security.web.server.SecurityWebFilterChain |
||||
import org.springframework.test.web.reactive.server.WebTestClient |
||||
import org.springframework.web.reactive.config.EnableWebFlux |
||||
|
||||
/** |
||||
* Tests for [ServerPasswordManagementDsl]. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
class ServerPasswordManagementDslTests { |
||||
|
||||
@Rule |
||||
@JvmField |
||||
val spring = SpringTestRule() |
||||
|
||||
private lateinit var client: WebTestClient |
||||
|
||||
@Autowired |
||||
fun setup(context: ApplicationContext) { |
||||
this.client = WebTestClient |
||||
.bindToApplicationContext(context) |
||||
.configureClient() |
||||
.build() |
||||
} |
||||
|
||||
@Test |
||||
fun `when change password page not set then default change password page used`() { |
||||
this.spring.register(PasswordManagementWithDefaultChangePasswordPageConfig::class.java).autowire() |
||||
|
||||
this.client.get() |
||||
.uri("/.well-known/change-password") |
||||
.exchange() |
||||
.expectStatus().isFound |
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "/change-password") |
||||
} |
||||
|
||||
@EnableWebFluxSecurity |
||||
@EnableWebFlux |
||||
open class PasswordManagementWithDefaultChangePasswordPageConfig { |
||||
@Bean |
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
||||
return http { |
||||
passwordManagement {} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `when change password page set then specified change password page used`() { |
||||
this.spring.register(PasswordManagementWithCustomChangePasswordPageConfig::class.java).autowire() |
||||
|
||||
this.client.get() |
||||
.uri("/.well-known/change-password") |
||||
.exchange() |
||||
.expectStatus().isFound |
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "/custom-change-password-page") |
||||
} |
||||
|
||||
@EnableWebFluxSecurity |
||||
@EnableWebFlux |
||||
open class PasswordManagementWithCustomChangePasswordPageConfig { |
||||
@Bean |
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
||||
return http { |
||||
passwordManagement { |
||||
changePasswordPage = "/custom-change-password-page" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.servlet |
||||
|
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.springframework.beans.factory.annotation.Autowired |
||||
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.test.web.servlet.MockMvc |
||||
import org.springframework.test.web.servlet.get |
||||
|
||||
/** |
||||
* Tests for [PasswordManagementDsl]. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
class PasswordManagementDslTests { |
||||
|
||||
@Rule |
||||
@JvmField |
||||
val spring = SpringTestRule() |
||||
|
||||
@Autowired |
||||
lateinit var mockMvc: MockMvc |
||||
|
||||
@Test |
||||
fun `when change password page not set then default change password page used`() { |
||||
this.spring.register(PasswordManagementWithDefaultChangePasswordPageConfig::class.java).autowire() |
||||
|
||||
this.mockMvc.get("/.well-known/change-password") |
||||
.andExpect { |
||||
status { isFound() } |
||||
redirectedUrl("/change-password") |
||||
} |
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
open class PasswordManagementWithDefaultChangePasswordPageConfig : WebSecurityConfigurerAdapter() { |
||||
override fun configure(http: HttpSecurity) { |
||||
http { |
||||
passwordManagement {} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `when change password page set then specified change password page used`() { |
||||
this.spring.register(PasswordManagementWithCustomChangePasswordPageConfig::class.java).autowire() |
||||
|
||||
this.mockMvc.get("/.well-known/change-password") |
||||
.andExpect { |
||||
status { isFound() } |
||||
redirectedUrl("/custom-change-password-page") |
||||
} |
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
open class PasswordManagementWithCustomChangePasswordPageConfig : WebSecurityConfigurerAdapter() { |
||||
override fun configure(http: HttpSecurity) { |
||||
http { |
||||
passwordManagement { |
||||
changePasswordPage = "/custom-change-password-page" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!-- |
||||
~ Copyright 2002-2021 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 |
||||
~ |
||||
~ https://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. |
||||
--> |
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xmlns="http://www.springframework.org/schema/security" |
||||
xsi:schemaLocation=" |
||||
http://www.springframework.org/schema/security |
||||
https://www.springframework.org/schema/security/spring-security.xsd |
||||
http://www.springframework.org/schema/beans |
||||
https://www.springframework.org/schema/beans/spring-beans.xsd"> |
||||
|
||||
<http auto-config="true"> |
||||
<password-management change-password-page="/custom-change-password-page"/> |
||||
</http> |
||||
|
||||
<b:import resource="userservice.xml"/> |
||||
</b:beans> |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!-- |
||||
~ Copyright 2002-2021 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 |
||||
~ |
||||
~ https://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. |
||||
--> |
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xmlns="http://www.springframework.org/schema/security" |
||||
xsi:schemaLocation=" |
||||
http://www.springframework.org/schema/security |
||||
https://www.springframework.org/schema/security/spring-security.xsd |
||||
http://www.springframework.org/schema/beans |
||||
https://www.springframework.org/schema/beans/spring-beans.xsd"> |
||||
|
||||
<http auto-config="true"> |
||||
<password-management/> |
||||
</http> |
||||
|
||||
<b:import resource="userservice.xml"/> |
||||
</b:beans> |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
/** |
||||
* Filter that redirects requests that match {@link RequestMatcher} to the specified URL. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @since 5.6 |
||||
*/ |
||||
public final class RequestMatcherRedirectFilter extends OncePerRequestFilter { |
||||
|
||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); |
||||
|
||||
private final RequestMatcher requestMatcher; |
||||
|
||||
private final String redirectUrl; |
||||
|
||||
/** |
||||
* Create and initialize an instance of the filter. |
||||
* @param requestMatcher the request matcher |
||||
* @param redirectUrl the redirect URL |
||||
*/ |
||||
public RequestMatcherRedirectFilter(RequestMatcher requestMatcher, String redirectUrl) { |
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null"); |
||||
Assert.hasText(redirectUrl, "redirectUrl cannot be empty"); |
||||
this.requestMatcher = requestMatcher; |
||||
this.redirectUrl = redirectUrl; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
|
||||
if (this.requestMatcher.matches(request)) { |
||||
this.redirectStrategy.sendRedirect(request, response, this.redirectUrl); |
||||
} |
||||
else { |
||||
filterChain.doFilter(request, response); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.server; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; |
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.server.WebFilter; |
||||
import org.springframework.web.server.WebFilterChain; |
||||
|
||||
/** |
||||
* Web filter that redirects requests that match {@link ServerWebExchangeMatcher} to the |
||||
* specified URL. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @since 5.6 |
||||
*/ |
||||
public final class ExchangeMatcherRedirectWebFilter implements WebFilter { |
||||
|
||||
private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); |
||||
|
||||
private final ServerWebExchangeMatcher exchangeMatcher; |
||||
|
||||
private final URI redirectUri; |
||||
|
||||
/** |
||||
* Create and initialize an instance of the web filter. |
||||
* @param exchangeMatcher the exchange matcher |
||||
* @param redirectUrl the redirect URL |
||||
*/ |
||||
public ExchangeMatcherRedirectWebFilter(ServerWebExchangeMatcher exchangeMatcher, String redirectUrl) { |
||||
Assert.notNull(exchangeMatcher, "exchangeMatcher cannot be null"); |
||||
Assert.hasText(redirectUrl, "redirectUrl cannot be empty"); |
||||
this.exchangeMatcher = exchangeMatcher; |
||||
this.redirectUri = URI.create(redirectUrl); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { |
||||
// @formatter:off
|
||||
return this.exchangeMatcher.matches(exchange) |
||||
.filter(MatchResult::isMatch) |
||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty())) |
||||
.flatMap((result) -> this.redirectStrategy.sendRedirect(exchange, this.redirectUri)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
/** |
||||
* Tests for {@link RequestMatcherRedirectFilter}. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
public class RequestMatcherRedirectFilterTests { |
||||
|
||||
@Test |
||||
public void doFilterWhenRequestMatchThenRedirectToSpecifiedUrl() throws Exception { |
||||
RequestMatcherRedirectFilter filter = new RequestMatcherRedirectFilter(new AntPathRequestMatcher("/context"), |
||||
"/test"); |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setServletPath("/context"); |
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); |
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/test"); |
||||
|
||||
verifyNoInteractions(filterChain); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenRequestNotMatchThenNextFilter() throws Exception { |
||||
RequestMatcherRedirectFilter filter = new RequestMatcherRedirectFilter(new AntPathRequestMatcher("/context"), |
||||
"/test"); |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setServletPath("/test"); |
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); |
||||
|
||||
verify(filterChain).doFilter(request, response); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRequestMatcherNull() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RequestMatcherRedirectFilter(null, "/test")) |
||||
.withMessage("requestMatcher cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRedirectUrlNull() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new RequestMatcherRedirectFilter(new AntPathRequestMatcher("/**"), null)) |
||||
.withMessage("redirectUrl cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRedirectUrlEmpty() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new RequestMatcherRedirectFilter(new AntPathRequestMatcher("/**"), "")) |
||||
.withMessage("redirectUrl cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRedirectUrlBlank() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new RequestMatcherRedirectFilter(new AntPathRequestMatcher("/**"), " ")) |
||||
.withMessage("redirectUrl cannot be empty"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 |
||||
* |
||||
* https://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.web.server; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; |
||||
import org.springframework.test.web.reactive.server.WebTestClient; |
||||
import org.springframework.web.server.handler.FilteringWebHandler; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link ExchangeMatcherRedirectWebFilter}. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
*/ |
||||
public class ExchangeMatcherRedirectWebFilterTests { |
||||
|
||||
@Test |
||||
public void filterWhenRequestMatchThenRedirectToSpecifiedUrl() { |
||||
ExchangeMatcherRedirectWebFilter filter = new ExchangeMatcherRedirectWebFilter( |
||||
new PathPatternParserServerWebExchangeMatcher("/context"), "/test"); |
||||
FilteringWebHandler handler = new FilteringWebHandler((e) -> e.getResponse().setComplete(), |
||||
Collections.singletonList(filter)); |
||||
|
||||
WebTestClient client = WebTestClient.bindToWebHandler(handler).build(); |
||||
client.get().uri("/context").exchange().expectStatus().isFound().expectHeader() |
||||
.valueEquals(HttpHeaders.LOCATION, "/test"); |
||||
} |
||||
|
||||
@Test |
||||
public void filterWhenRequestNotMatchThenNextFilter() { |
||||
ExchangeMatcherRedirectWebFilter filter = new ExchangeMatcherRedirectWebFilter( |
||||
new PathPatternParserServerWebExchangeMatcher("/context"), "/test"); |
||||
FilteringWebHandler handler = new FilteringWebHandler((e) -> e.getResponse().setComplete(), |
||||
Collections.singletonList(filter)); |
||||
|
||||
WebTestClient client = WebTestClient.bindToWebHandler(handler).build(); |
||||
client.get().uri("/test").exchange().expectStatus().isOk(); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenExchangeMatcherNull() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ExchangeMatcherRedirectWebFilter(null, "/test")) |
||||
.withMessage("exchangeMatcher cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRedirectUrlNull() { |
||||
assertThatIllegalArgumentException().isThrownBy( |
||||
() -> new ExchangeMatcherRedirectWebFilter(new PathPatternParserServerWebExchangeMatcher("/**"), null)) |
||||
.withMessage("redirectUrl cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRedirectUrlEmpty() { |
||||
assertThatIllegalArgumentException().isThrownBy( |
||||
() -> new ExchangeMatcherRedirectWebFilter(new PathPatternParserServerWebExchangeMatcher("/**"), "")) |
||||
.withMessage("redirectUrl cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void constructWhenRedirectUrlBlank() { |
||||
assertThatIllegalArgumentException().isThrownBy( |
||||
() -> new ExchangeMatcherRedirectWebFilter(new PathPatternParserServerWebExchangeMatcher("/**"), " ")) |
||||
.withMessage("redirectUrl cannot be empty"); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue