Skip to content

Add Password Advice Support #17118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3321,7 +3321,9 @@ public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> http
*/
public HttpSecurity passwordManagement(
Customizer<PasswordManagementConfigurer<HttpSecurity>> passwordManagementCustomizer) throws Exception {
passwordManagementCustomizer.customize(getOrApply(new PasswordManagementConfigurer<>()));
PasswordManagementConfigurer<HttpSecurity> passwordManagement = new PasswordManagementConfigurer<>();
passwordManagement.setApplicationContext(getContext());
passwordManagementCustomizer.customize(getOrApply(passwordManagement));
return HttpSecurity.this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
import org.springframework.security.web.authentication.password.ChangePasswordAdviceMethodArgumentResolver;
import org.springframework.security.web.authentication.password.ChangePasswordAdviceRepository;
import org.springframework.security.web.authentication.password.HttpSessionChangePasswordAdviceRepository;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.security.web.firewall.HttpFirewall;
Expand Down Expand Up @@ -87,6 +90,8 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex

private AnnotationTemplateExpressionDefaults templateDefaults;

private ChangePasswordAdviceRepository changePasswordAdviceRepository = new HttpSessionChangePasswordAdviceRepository();

@Override
@SuppressWarnings("deprecation")
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
Expand All @@ -103,6 +108,9 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
currentSecurityContextArgumentResolver.setTemplateDefaults(this.templateDefaults);
argumentResolvers.add(currentSecurityContextArgumentResolver);
argumentResolvers.add(new CsrfTokenArgumentResolver());
ChangePasswordAdviceMethodArgumentResolver resolver = new ChangePasswordAdviceMethodArgumentResolver();
resolver.setChangePasswordAdviceRepository(this.changePasswordAdviceRepository);
argumentResolvers.add(resolver);
}

@Bean
Expand All @@ -119,6 +127,9 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
if (applicationContext.getBeanNamesForType(AnnotationTemplateExpressionDefaults.class).length == 1) {
this.templateDefaults = applicationContext.getBean(AnnotationTemplateExpressionDefaults.class);
}
if (applicationContext.getBeanNamesForType(ChangePasswordAdviceRepository.class).length == 1) {
this.changePasswordAdviceRepository = applicationContext.getBean(ChangePasswordAdviceRepository.class);
}
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private String getUsernameParameter() {
* Gets the HTTP parameter that is used to submit the password.
* @return the HTTP parameter that is used to submit the password
*/
private String getPasswordParameter() {
String getPasswordParameter() {
return getAuthenticationFilter().getPasswordParameter();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,33 @@

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

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.authentication.password.ChangePasswordAdvice;
import org.springframework.security.authentication.password.ChangePasswordAdvisor;
import org.springframework.security.authentication.password.ChangePasswordServiceAdvisor;
import org.springframework.security.authentication.password.DelegatingChangePasswordAdvisor;
import org.springframework.security.authentication.password.UserDetailsPasswordManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.RequestMatcherRedirectFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.password.ChangeCompromisedPasswordAdvisor;
import org.springframework.security.web.authentication.password.ChangePasswordAdviceHandler;
import org.springframework.security.web.authentication.password.ChangePasswordAdviceRepository;
import org.springframework.security.web.authentication.password.ChangePasswordAdvisingFilter;
import org.springframework.security.web.authentication.password.ChangePasswordProcessingFilter;
import org.springframework.security.web.authentication.password.DefaultChangePasswordPageGeneratingFilter;
import org.springframework.security.web.authentication.password.HttpSessionChangePasswordAdviceRepository;
import org.springframework.security.web.authentication.password.SimpleChangePasswordAdviceHandler;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.util.Assert;

/**
Expand All @@ -29,14 +52,28 @@
* @since 5.6
*/
public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>>
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> {
extends AbstractHttpConfigurer<PasswordManagementConfigurer<B>, B> implements ApplicationContextAware {

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 DEFAULT_CHANGE_PASSWORD_PAGE = DefaultChangePasswordPageGeneratingFilter.DEFAULT_CHANGE_PASSWORD_URL;

private ApplicationContext context;

private boolean customChangePasswordPage = false;

private String changePasswordPage = DEFAULT_CHANGE_PASSWORD_PAGE;

private String changePasswordProcessingUrl = ChangePasswordProcessingFilter.DEFAULT_PASSWORD_CHANGE_PROCESSING_URL;

private ChangePasswordAdviceRepository changePasswordAdviceRepository;

private ChangePasswordAdvisor changePasswordAdvisor;

private ChangePasswordAdviceHandler changePasswordAdviceHandler;

private UserDetailsPasswordManager userDetailsPasswordManager;

/**
* Sets the change password page. Defaults to
* {@link PasswordManagementConfigurer#DEFAULT_CHANGE_PASSWORD_PAGE}.
Expand All @@ -46,9 +83,76 @@ public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>
public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordPage) {
Assert.hasText(changePasswordPage, "changePasswordPage cannot be empty");
this.changePasswordPage = changePasswordPage;
this.customChangePasswordPage = true;
return this;
}

public PasswordManagementConfigurer<B> changePasswordProcessingUrl(String changePasswordProcessingUrl) {
this.changePasswordProcessingUrl = changePasswordProcessingUrl;
return this;
}

public PasswordManagementConfigurer<B> changePasswordAdviceRepository(
ChangePasswordAdviceRepository changePasswordAdviceRepository) {
this.changePasswordAdviceRepository = changePasswordAdviceRepository;
return this;
}

public PasswordManagementConfigurer<B> changePasswordAdvisor(ChangePasswordAdvisor changePasswordAdvisor) {
this.changePasswordAdvisor = changePasswordAdvisor;
return this;
}

public PasswordManagementConfigurer<B> changePasswordAdviceHandler(
ChangePasswordAdviceHandler changePasswordAdviceHandler) {
this.changePasswordAdviceHandler = changePasswordAdviceHandler;
return this;
}

public PasswordManagementConfigurer<B> userDetailsPasswordManager(
UserDetailsPasswordManager userDetailsPasswordManager) {
this.userDetailsPasswordManager = userDetailsPasswordManager;
return this;
}

@Override
public void init(B http) throws Exception {
UserDetailsPasswordManager passwordManager = (this.userDetailsPasswordManager == null)
? this.context.getBeanProvider(UserDetailsPasswordManager.class).getIfUnique()
: this.userDetailsPasswordManager;

if (passwordManager == null) {
return;
}

ChangePasswordAdviceRepository changePasswordAdviceRepository = (this.changePasswordAdviceRepository != null)
? this.changePasswordAdviceRepository
: this.context.getBeanProvider(ChangePasswordAdviceRepository.class)
.getIfUnique(HttpSessionChangePasswordAdviceRepository::new);

ChangePasswordAdvisor changePasswordAdvisor = (this.changePasswordAdvisor != null) ? this.changePasswordAdvisor
: this.context.getBeanProvider(ChangePasswordAdvisor.class).getIfUnique(() -> {
List<ChangePasswordAdvisor> advisors = new ArrayList<>();
advisors.add(new ChangeCompromisedPasswordAdvisor());
advisors.add(new ChangePasswordServiceAdvisor(passwordManager));
return new DelegatingChangePasswordAdvisor(advisors);
});

http.setSharedObject(ChangePasswordAdviceRepository.class, changePasswordAdviceRepository);
http.setSharedObject(UserDetailsPasswordManager.class, passwordManager);
http.setSharedObject(ChangePasswordAdvisor.class, changePasswordAdvisor);

FormLoginConfigurer form = http.getConfigurer(FormLoginConfigurer.class);
String passwordParameter = (form != null) ? form.getPasswordParameter() : "password";
http.getConfigurer(SessionManagementConfigurer.class)
.addSessionAuthenticationStrategy((authentication, request, response) -> {
UserDetails user = (UserDetails) authentication.getPrincipal();
String password = request.getParameter(passwordParameter);
ChangePasswordAdvice advice = changePasswordAdvisor.advise(user, password);
changePasswordAdviceRepository.savePasswordAdvice(request, response, advice);
});
}

/**
* {@inheritDoc}
*/
Expand All @@ -57,6 +161,42 @@ public void configure(B http) throws Exception {
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
RequestMatcherFactory.matcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);

if (http.getSharedObject(UserDetailsPasswordManager.class) == null) {
return;
}

PasswordEncoder passwordEncoder = this.context.getBeanProvider(PasswordEncoder.class)
.getIfUnique(PasswordEncoderFactories::createDelegatingPasswordEncoder);

ChangePasswordAdviceHandler changePasswordAdviceHandler = (this.changePasswordAdviceHandler != null)
? this.changePasswordAdviceHandler : this.context.getBeanProvider(ChangePasswordAdviceHandler.class)
.getIfUnique(() -> new SimpleChangePasswordAdviceHandler(this.changePasswordPage));

if (!this.customChangePasswordPage) {
DefaultChangePasswordPageGeneratingFilter page = new DefaultChangePasswordPageGeneratingFilter();
http.addFilterBefore(page, RequestCacheAwareFilter.class);
}

ChangePasswordProcessingFilter processing = new ChangePasswordProcessingFilter(
http.getSharedObject(UserDetailsPasswordManager.class));
processing
.setRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher(this.changePasswordProcessingUrl));
processing.setChangePasswordAdvisor(http.getSharedObject(ChangePasswordAdvisor.class));
processing.setChangePasswordAdviceRepository(http.getSharedObject(ChangePasswordAdviceRepository.class));
processing.setPasswordEncoder(passwordEncoder);
processing.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
http.addFilterBefore(processing, RequestCacheAwareFilter.class);

ChangePasswordAdvisingFilter advising = new ChangePasswordAdvisingFilter();
advising.setChangePasswordAdviceRepository(http.getSharedObject(ChangePasswordAdviceRepository.class));
advising.setChangePasswordAdviceHandler(changePasswordAdviceHandler);
http.addFilterBefore(advising, RequestCacheAwareFilter.class);
}

@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}

}
Loading