Skip to content

Commit cd4699f

Browse files
committed
Support A Well-Known URL for Changing Passwords
Closes gh-8657
1 parent da9d741 commit cd4699f

File tree

25 files changed

+1307
-4
lines changed

25 files changed

+1307
-4
lines changed

config/src/main/java/org/springframework/security/config/Elements.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
* Contains all the element names used by Spring Security 3 namespace support.
2121
*
2222
* @author Ben Alex
23+
* @author Evgeniy Cheban
2324
*/
2425
public abstract class Elements {
2526

@@ -135,4 +136,6 @@ public abstract class Elements {
135136

136137
public static final String CLIENT_REGISTRATIONS = "client-registrations";
137138

139+
public static final String PASSWORD_MANAGEMENT = "password-management";
140+
138141
}

config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

+40
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
6060
import org.springframework.security.config.annotation.web.configurers.JeeConfigurer;
6161
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
62+
import org.springframework.security.config.annotation.web.configurers.PasswordManagementConfigurer;
6263
import org.springframework.security.config.annotation.web.configurers.PortMapperConfigurer;
6364
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
6465
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
@@ -2604,6 +2605,45 @@ public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> http
26042605
return HttpSecurity.this;
26052606
}
26062607

2608+
/**
2609+
* Adds support for the password management.
2610+
*
2611+
* <h2>Example Configuration</h2> The example below demonstrates how to configure
2612+
* password management for an application. The default change password page is
2613+
* "/change-password", but can be customized using
2614+
* {@link PasswordManagementConfigurer#changePasswordPage(String)}.
2615+
*
2616+
* <pre>
2617+
* &#064;Configuration
2618+
* &#064;EnableWebSecurity
2619+
* public class PasswordManagementSecurityConfig extends WebSecurityConfigurerAdapter {
2620+
*
2621+
* &#064;Override
2622+
* protected void configure(HttpSecurity http) throws Exception {
2623+
* http
2624+
* .authorizeRequests(authorizeRequests ->
2625+
* authorizeRequests
2626+
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
2627+
* )
2628+
* .passwordManagement(passwordManagement ->
2629+
* passwordManagement
2630+
* .changePasswordPage(&quot;/custom-change-password-page&quot;)
2631+
* );
2632+
* }
2633+
* }
2634+
* </pre>
2635+
* @param passwordManagementCustomizer the {@link Customizer} to provide more options
2636+
* for the {@link PasswordManagementConfigurer}
2637+
* @return the {@link HttpSecurity} for further customizations
2638+
* @throws Exception
2639+
* @since 5.6
2640+
*/
2641+
public HttpSecurity passwordManagement(
2642+
Customizer<PasswordManagementConfigurer<HttpSecurity>> passwordManagementCustomizer) throws Exception {
2643+
passwordManagementCustomizer.customize(getOrApply(new PasswordManagementConfigurer<>()));
2644+
return HttpSecurity.this;
2645+
}
2646+
26072647
@Override
26082648
public <C> void setSharedObject(Class<C> sharedType, C object) {
26092649
super.setSharedObject(sharedType, object);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web.configurers;
18+
19+
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
20+
import org.springframework.security.web.RequestMatcherRedirectFilter;
21+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
22+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* Adds password management support.
27+
*
28+
* @author Evgeniy Cheban
29+
* @since 5.6
30+
*/
31+
public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>>
32+
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> {
33+
34+
private static final String WELL_KNOWN_CHANGE_PASSWORD_PATTERN = "/.well-known/change-password";
35+
36+
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = "/change-password";
37+
38+
private String changePasswordPage = DEFAULT_CHANGE_PASSWORD_PAGE;
39+
40+
/**
41+
* Sets the change password page. Defaults to
42+
* {@link PasswordManagementConfigurer#DEFAULT_CHANGE_PASSWORD_PAGE}.
43+
* @param changePasswordPage the change password page
44+
* @return the {@link PasswordManagementConfigurer} for further customizations
45+
*/
46+
public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordPage) {
47+
Assert.hasText(changePasswordPage, "changePasswordPage cannot be empty");
48+
this.changePasswordPage = changePasswordPage;
49+
return this;
50+
}
51+
52+
/**
53+
* {@inheritDoc}
54+
*/
55+
@Override
56+
public void configure(B http) throws Exception {
57+
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
58+
new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
59+
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
60+
}
61+
62+
}

config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

+16
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ class HttpConfigurationBuilder {
176176

177177
private BeanDefinition csrfFilter;
178178

179+
private BeanDefinition wellKnownChangePasswordRedirectFilter;
180+
179181
private BeanMetadataElement csrfLogoutHandler;
180182

181183
private BeanMetadataElement csrfAuthStrategy;
@@ -210,6 +212,7 @@ class HttpConfigurationBuilder {
210212
createFilterSecurityInterceptor(authenticationManager);
211213
createAddHeadersFilter();
212214
createCorsFilter();
215+
createWellKnownChangePasswordRedirectFilter();
213216
}
214217

215218
private void validateInterceptUrls(ParserContext pc) {
@@ -694,6 +697,15 @@ private void createCsrfFilter() {
694697
this.csrfLogoutHandler = this.csrfParser.getCsrfLogoutHandler();
695698
}
696699

700+
private void createWellKnownChangePasswordRedirectFilter() {
701+
Element element = DomUtils.getChildElementByTagName(this.httpElt, Elements.PASSWORD_MANAGEMENT);
702+
if (element == null) {
703+
return;
704+
}
705+
WellKnownChangePasswordBeanDefinitionParser parser = new WellKnownChangePasswordBeanDefinitionParser();
706+
this.wellKnownChangePasswordRedirectFilter = parser.parse(element, this.pc);
707+
}
708+
697709
BeanMetadataElement getCsrfLogoutHandler() {
698710
return this.csrfLogoutHandler;
699711
}
@@ -744,6 +756,10 @@ List<OrderDecorator> getFilters() {
744756
if (this.csrfFilter != null) {
745757
filters.add(new OrderDecorator(this.csrfFilter, SecurityFilters.CSRF_FILTER));
746758
}
759+
if (this.wellKnownChangePasswordRedirectFilter != null) {
760+
filters.add(new OrderDecorator(this.wellKnownChangePasswordRedirectFilter,
761+
SecurityFilters.WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER));
762+
}
747763
return filters;
748764
}
749765

config/src/main/java/org/springframework/security/config/http/SecurityFilters.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
*
2323
* @author Luke Taylor
2424
* @author Rob Winch
25+
* @author Evgeniy Cheban
2526
*/
2627

2728
enum SecurityFilters {
@@ -80,6 +81,8 @@ enum SecurityFilters {
8081

8182
OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER,
8283

84+
WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER,
85+
8386
SESSION_MANAGEMENT_FILTER,
8487

8588
EXCEPTION_TRANSLATION_FILTER,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.http;
18+
19+
import org.w3c.dom.Element;
20+
21+
import org.springframework.beans.factory.config.BeanDefinition;
22+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
23+
import org.springframework.beans.factory.xml.BeanDefinitionParser;
24+
import org.springframework.beans.factory.xml.ParserContext;
25+
import org.springframework.security.web.RequestMatcherRedirectFilter;
26+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
27+
import org.springframework.util.StringUtils;
28+
29+
/**
30+
* The bean definition parser for a Well-Known URL for Changing Passwords.
31+
*
32+
* @author Evgeniy Cheban
33+
* @since 5.6
34+
*/
35+
public final class WellKnownChangePasswordBeanDefinitionParser implements BeanDefinitionParser {
36+
37+
private static final String WELL_KNOWN_CHANGE_PASSWORD_PATTERN = "/.well-known/change-password";
38+
39+
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = "/change-password";
40+
41+
private static final String ATT_CHANGE_PASSWORD_PAGE = "change-password-page";
42+
43+
/**
44+
* {@inheritDoc}
45+
*/
46+
@Override
47+
public BeanDefinition parse(Element element, ParserContext parserContext) {
48+
BeanDefinition changePasswordFilter = BeanDefinitionBuilder
49+
.rootBeanDefinition(RequestMatcherRedirectFilter.class)
50+
.addConstructorArgValue(new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN))
51+
.addConstructorArgValue(getChangePasswordPage(element)).getBeanDefinition();
52+
parserContext.getReaderContext().registerWithGeneratedName(changePasswordFilter);
53+
return changePasswordFilter;
54+
}
55+
56+
private String getChangePasswordPage(Element element) {
57+
String changePasswordPage = element.getAttribute(ATT_CHANGE_PASSWORD_PAGE);
58+
return (StringUtils.hasText(changePasswordPage) ? changePasswordPage : DEFAULT_CHANGE_PASSWORD_PAGE);
59+
}
60+
61+
}

0 commit comments

Comments
 (0)