Skip to content

Commit 9961445

Browse files
committed
Add Password Advice Support
1 parent 86550fb commit 9961445

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
@@ -3321,7 +3321,9 @@ public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> http
33213321
*/
33223322
public HttpSecurity passwordManagement(
33233323
Customizer<PasswordManagementConfigurer<HttpSecurity>> passwordManagementCustomizer) throws Exception {
3324-
passwordManagementCustomizer.customize(getOrApply(new PasswordManagementConfigurer<>()));
3324+
PasswordManagementConfigurer<HttpSecurity> passwordManagement = new PasswordManagementConfigurer<>();
3325+
passwordManagement.setApplicationContext(getContext());
3326+
passwordManagementCustomizer.customize(getOrApply(passwordManagement));
33253327
return HttpSecurity.this;
33263328
}
33273329

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
@@ -46,6 +46,9 @@
4646
import org.springframework.security.web.FilterChainProxy;
4747
import org.springframework.security.web.SecurityFilterChain;
4848
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
49+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceMethodArgumentResolver;
50+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceRepository;
51+
import org.springframework.security.web.authentication.password.HttpSessionChangePasswordAdviceRepository;
4952
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
5053
import org.springframework.security.web.debug.DebugFilter;
5154
import org.springframework.security.web.firewall.HttpFirewall;
@@ -87,6 +90,8 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
8790

8891
private AnnotationTemplateExpressionDefaults templateDefaults;
8992

93+
private ChangePasswordAdviceRepository changePasswordAdviceRepository = new HttpSessionChangePasswordAdviceRepository();
94+
9095
@Override
9196
@SuppressWarnings("deprecation")
9297
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
@@ -103,6 +108,9 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
103108
currentSecurityContextArgumentResolver.setTemplateDefaults(this.templateDefaults);
104109
argumentResolvers.add(currentSecurityContextArgumentResolver);
105110
argumentResolvers.add(new CsrfTokenArgumentResolver());
111+
ChangePasswordAdviceMethodArgumentResolver resolver = new ChangePasswordAdviceMethodArgumentResolver();
112+
resolver.setChangePasswordAdviceRepository(this.changePasswordAdviceRepository);
113+
argumentResolvers.add(resolver);
106114
}
107115

108116
@Bean
@@ -119,6 +127,9 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
119127
if (applicationContext.getBeanNamesForType(AnnotationTemplateExpressionDefaults.class).length == 1) {
120128
this.templateDefaults = applicationContext.getBean(AnnotationTemplateExpressionDefaults.class);
121129
}
130+
if (applicationContext.getBeanNamesForType(ChangePasswordAdviceRepository.class).length == 1) {
131+
this.changePasswordAdviceRepository = applicationContext.getBean(ChangePasswordAdviceRepository.class);
132+
}
122133
}
123134

124135
@Bean

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,10 +16,33 @@
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;
2030
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
31+
import org.springframework.security.core.userdetails.UserDetails;
32+
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
33+
import org.springframework.security.crypto.password.PasswordEncoder;
2134
import org.springframework.security.web.RequestMatcherRedirectFilter;
2235
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
36+
import org.springframework.security.web.authentication.password.ChangeCompromisedPasswordAdvisor;
37+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceHandler;
38+
import org.springframework.security.web.authentication.password.ChangePasswordAdviceRepository;
39+
import org.springframework.security.web.authentication.password.ChangePasswordAdvisingFilter;
40+
import org.springframework.security.web.authentication.password.ChangePasswordProcessingFilter;
41+
import org.springframework.security.web.authentication.password.DefaultChangePasswordPageGeneratingFilter;
42+
import org.springframework.security.web.authentication.password.HttpSessionChangePasswordAdviceRepository;
43+
import org.springframework.security.web.authentication.password.SimpleChangePasswordAdviceHandler;
44+
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
45+
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
2346
import org.springframework.util.Assert;
2447

2548
/**
@@ -29,14 +52,28 @@
2952
* @since 5.6
3053
*/
3154
public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>>
32-
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> {
55+
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> implements ApplicationContextAware {
3356

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

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

3865
private String changePasswordPage = DEFAULT_CHANGE_PASSWORD_PAGE;
3966

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

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

62202
}

0 commit comments

Comments
 (0)