Skip to content

Commit ce01188

Browse files
committed
Add Password Advice Support
1 parent 517a2b1 commit ce01188

24 files changed

+1404
-6
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1778,7 +1778,9 @@ public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> http
17781778
*/
17791779
public HttpSecurity passwordManagement(
17801780
Customizer<PasswordManagementConfigurer<HttpSecurity>> passwordManagementCustomizer) throws Exception {
1781-
passwordManagementCustomizer.customize(getOrApply(new PasswordManagementConfigurer<>()));
1781+
PasswordManagementConfigurer<HttpSecurity> passwordManagement = new PasswordManagementConfigurer<>();
1782+
passwordManagement.setApplicationContext(getContext());
1783+
passwordManagementCustomizer.customize(getOrApply(passwordManagement));
17821784
return HttpSecurity.this;
17831785
}
17841786

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3838
import org.springframework.security.web.FilterChainProxy;
3939
import org.springframework.security.web.SecurityFilterChain;
40+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceMethodArgumentResolver;
41+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceRepository;
42+
import org.springframework.security.web.authentication.password.HttpSessionChangePasswordAdviceRepository;
4043
import org.springframework.security.web.debug.DebugFilter;
4144
import org.springframework.security.web.firewall.HttpFirewall;
4245
import org.springframework.security.web.firewall.RequestRejectedHandler;
@@ -72,6 +75,8 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
7275

7376
private AnnotationTemplateExpressionDefaults templateDefaults;
7477

78+
private ChangePasswordAdviceRepository changePasswordAdviceRepository = new HttpSessionChangePasswordAdviceRepository();
79+
7580
@Override
7681
@SuppressWarnings("deprecation")
7782
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
@@ -88,6 +93,9 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
8893
currentSecurityContextArgumentResolver.setTemplateDefaults(this.templateDefaults);
8994
argumentResolvers.add(currentSecurityContextArgumentResolver);
9095
argumentResolvers.add(new CsrfTokenArgumentResolver());
96+
ChangePasswordAdviceMethodArgumentResolver resolver = new ChangePasswordAdviceMethodArgumentResolver();
97+
resolver.setChangePasswordAdviceRepository(this.changePasswordAdviceRepository);
98+
argumentResolvers.add(resolver);
9199
}
92100

93101
@Bean
@@ -104,6 +112,9 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
104112
if (applicationContext.getBeanNamesForType(AnnotationTemplateExpressionDefaults.class).length == 1) {
105113
this.templateDefaults = applicationContext.getBean(AnnotationTemplateExpressionDefaults.class);
106114
}
115+
if (applicationContext.getBeanNamesForType(ChangePasswordAdviceRepository.class).length == 1) {
116+
this.changePasswordAdviceRepository = applicationContext.getBean(ChangePasswordAdviceRepository.class);
117+
}
107118
}
108119

109120
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ private String getUsernameParameter() {
250250
* Gets the HTTP parameter that is used to submit the password.
251251
* @return the HTTP parameter that is used to submit the password
252252
*/
253-
private String getPasswordParameter() {
253+
String getPasswordParameter() {
254254
return getAuthenticationFilter().getPasswordParameter();
255255
}
256256

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

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,32 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.context.ApplicationContext;
23+
import org.springframework.context.ApplicationContextAware;
24+
import org.springframework.security.authentication.password.ChangePasswordAdvice;
25+
import org.springframework.security.authentication.password.ChangePasswordAdvisor;
26+
import org.springframework.security.authentication.password.ChangePasswordServiceAdvisor;
27+
import org.springframework.security.authentication.password.DelegatingChangePasswordAdvisor;
28+
import org.springframework.security.authentication.password.UserDetailsPasswordManager;
1929
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
30+
import org.springframework.security.core.userdetails.UserDetails;
31+
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
32+
import org.springframework.security.crypto.password.PasswordEncoder;
2033
import org.springframework.security.web.RequestMatcherRedirectFilter;
2134
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
35+
import org.springframework.security.web.authentication.password.ChangeCompromisedPasswordAdvisor;
36+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceHandler;
37+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceRepository;
38+
import org.springframework.security.web.authentication.password.ChangePasswordAdvisingFilter;
39+
import org.springframework.security.web.authentication.password.ChangePasswordProcessingFilter;
40+
import org.springframework.security.web.authentication.password.DefaultChangePasswordPageGeneratingFilter;
41+
import org.springframework.security.web.authentication.password.HttpSessionChangePasswordAdviceRepository;
42+
import org.springframework.security.web.authentication.password.SimpleChangePasswordAdviceHandler;
43+
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
44+
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
2245
import org.springframework.util.Assert;
2346

2447
/**
@@ -28,14 +51,28 @@
2851
* @since 5.6
2952
*/
3053
public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>>
31-
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> {
54+
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> implements ApplicationContextAware {
3255

3356
private static final String WELL_KNOWN_CHANGE_PASSWORD_PATTERN = "/.well-known/change-password";
3457

35-
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = "/change-password";
58+
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = DefaultChangePasswordPageGeneratingFilter.DEFAULT_CHANGE_PASSWORD_URL;
59+
60+
private ApplicationContext context;
61+
62+
private boolean customChangePasswordPage = false;
3663

3764
private String changePasswordPage = DEFAULT_CHANGE_PASSWORD_PAGE;
3865

66+
private String changePasswordProcessingUrl = ChangePasswordProcessingFilter.DEFAULT_PASSWORD_CHANGE_PROCESSING_URL;
67+
68+
private ChangePasswordAdviceRepository changePasswordAdviceRepository;
69+
70+
private ChangePasswordAdvisor changePasswordAdvisor;
71+
72+
private ChangePasswordAdviceHandler changePasswordAdviceHandler;
73+
74+
private UserDetailsPasswordManager userDetailsPasswordManager;
75+
3976
/**
4077
* Sets the change password page. Defaults to
4178
* {@link PasswordManagementConfigurer#DEFAULT_CHANGE_PASSWORD_PAGE}.
@@ -45,9 +82,76 @@ public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>
4582
public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordPage) {
4683
Assert.hasText(changePasswordPage, "changePasswordPage cannot be empty");
4784
this.changePasswordPage = changePasswordPage;
85+
this.customChangePasswordPage = true;
86+
return this;
87+
}
88+
89+
public PasswordManagementConfigurer<B> changePasswordProcessingUrl(String changePasswordProcessingUrl) {
90+
this.changePasswordProcessingUrl = changePasswordProcessingUrl;
91+
return this;
92+
}
93+
94+
public PasswordManagementConfigurer<B> changePasswordAdviceRepository(
95+
ChangePasswordAdviceRepository changePasswordAdviceRepository) {
96+
this.changePasswordAdviceRepository = changePasswordAdviceRepository;
97+
return this;
98+
}
99+
100+
public PasswordManagementConfigurer<B> changePasswordAdvisor(ChangePasswordAdvisor changePasswordAdvisor) {
101+
this.changePasswordAdvisor = changePasswordAdvisor;
102+
return this;
103+
}
104+
105+
public PasswordManagementConfigurer<B> changePasswordAdviceHandler(
106+
ChangePasswordAdviceHandler changePasswordAdviceHandler) {
107+
this.changePasswordAdviceHandler = changePasswordAdviceHandler;
48108
return this;
49109
}
50110

111+
public PasswordManagementConfigurer<B> userDetailsPasswordManager(
112+
UserDetailsPasswordManager userDetailsPasswordManager) {
113+
this.userDetailsPasswordManager = userDetailsPasswordManager;
114+
return this;
115+
}
116+
117+
@Override
118+
public void init(B http) throws Exception {
119+
UserDetailsPasswordManager passwordManager = (this.userDetailsPasswordManager == null)
120+
? this.context.getBeanProvider(UserDetailsPasswordManager.class).getIfUnique()
121+
: this.userDetailsPasswordManager;
122+
123+
if (passwordManager == null) {
124+
return;
125+
}
126+
127+
ChangePasswordAdviceRepository changePasswordAdviceRepository = (this.changePasswordAdviceRepository != null)
128+
? this.changePasswordAdviceRepository
129+
: this.context.getBeanProvider(ChangePasswordAdviceRepository.class)
130+
.getIfUnique(HttpSessionChangePasswordAdviceRepository::new);
131+
132+
ChangePasswordAdvisor changePasswordAdvisor = (this.changePasswordAdvisor != null) ? this.changePasswordAdvisor
133+
: this.context.getBeanProvider(ChangePasswordAdvisor.class).getIfUnique(() -> {
134+
List<ChangePasswordAdvisor> advisors = new ArrayList<>();
135+
advisors.add(new ChangeCompromisedPasswordAdvisor());
136+
advisors.add(new ChangePasswordServiceAdvisor(passwordManager));
137+
return new DelegatingChangePasswordAdvisor(advisors);
138+
});
139+
140+
http.setSharedObject(ChangePasswordAdviceRepository.class, changePasswordAdviceRepository);
141+
http.setSharedObject(UserDetailsPasswordManager.class, passwordManager);
142+
http.setSharedObject(ChangePasswordAdvisor.class, changePasswordAdvisor);
143+
144+
FormLoginConfigurer form = http.getConfigurer(FormLoginConfigurer.class);
145+
String passwordParameter = (form != null) ? form.getPasswordParameter() : "password";
146+
http.getConfigurer(SessionManagementConfigurer.class)
147+
.addSessionAuthenticationStrategy((authentication, request, response) -> {
148+
UserDetails user = (UserDetails) authentication.getPrincipal();
149+
String password = request.getParameter(passwordParameter);
150+
ChangePasswordAdvice advice = changePasswordAdvisor.advise(user, password);
151+
changePasswordAdviceRepository.savePasswordAdvice(request, response, advice);
152+
});
153+
}
154+
51155
/**
52156
* {@inheritDoc}
53157
*/
@@ -56,6 +160,42 @@ public void configure(B http) throws Exception {
56160
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
57161
getRequestMatcherBuilder().matcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
58162
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
163+
164+
if (http.getSharedObject(UserDetailsPasswordManager.class) == null) {
165+
return;
166+
}
167+
168+
PasswordEncoder passwordEncoder = this.context.getBeanProvider(PasswordEncoder.class)
169+
.getIfUnique(PasswordEncoderFactories::createDelegatingPasswordEncoder);
170+
171+
ChangePasswordAdviceHandler changePasswordAdviceHandler = (this.changePasswordAdviceHandler != null)
172+
? this.changePasswordAdviceHandler : this.context.getBeanProvider(ChangePasswordAdviceHandler.class)
173+
.getIfUnique(() -> new SimpleChangePasswordAdviceHandler(this.changePasswordPage));
174+
175+
if (!this.customChangePasswordPage) {
176+
DefaultChangePasswordPageGeneratingFilter page = new DefaultChangePasswordPageGeneratingFilter();
177+
http.addFilterBefore(page, RequestCacheAwareFilter.class);
178+
}
179+
180+
ChangePasswordProcessingFilter processing = new ChangePasswordProcessingFilter(
181+
http.getSharedObject(UserDetailsPasswordManager.class));
182+
processing
183+
.setRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher(this.changePasswordProcessingUrl));
184+
processing.setChangePasswordAdvisor(http.getSharedObject(ChangePasswordAdvisor.class));
185+
processing.setChangePasswordAdviceRepository(http.getSharedObject(ChangePasswordAdviceRepository.class));
186+
processing.setPasswordEncoder(passwordEncoder);
187+
processing.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
188+
http.addFilterBefore(processing, RequestCacheAwareFilter.class);
189+
190+
ChangePasswordAdvisingFilter advising = new ChangePasswordAdvisingFilter();
191+
advising.setChangePasswordAdviceRepository(http.getSharedObject(ChangePasswordAdviceRepository.class));
192+
advising.setChangePasswordAdviceHandler(changePasswordAdviceHandler);
193+
http.addFilterBefore(advising, RequestCacheAwareFilter.class);
194+
}
195+
196+
@Override
197+
public void setApplicationContext(ApplicationContext context) {
198+
this.context = context;
59199
}
60200

61201
}

0 commit comments

Comments
 (0)