Skip to content

Commit 6d657ea

Browse files
MrJovanovic13jzheaux
authored andcommitted
InMemoryUserDetailsManager preserve user type
Closes gh-3192
1 parent 503d653 commit 6d657ea

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -30,6 +30,7 @@
3030
import org.springframework.security.authentication.AuthenticationManager;
3131
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
3232
import org.springframework.security.core.Authentication;
33+
import org.springframework.security.core.CredentialsContainer;
3334
import org.springframework.security.core.context.SecurityContextHolder;
3435
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3536
import org.springframework.security.core.userdetails.User;
@@ -96,7 +97,13 @@ private User createUserDetails(String name, UserAttribute attr) {
9697
@Override
9798
public void createUser(UserDetails user) {
9899
Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
99-
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
100+
101+
if (user instanceof MutableUserDetails mutable) {
102+
this.users.put(user.getUsername().toLowerCase(), mutable);
103+
}
104+
else {
105+
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
106+
}
100107
}
101108

102109
@Override
@@ -107,7 +114,13 @@ public void deleteUser(String username) {
107114
@Override
108115
public void updateUser(UserDetails user) {
109116
Assert.isTrue(userExists(user.getUsername()), "user should exist");
110-
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
117+
118+
if (user instanceof MutableUserDetails mutable) {
119+
this.users.put(user.getUsername().toLowerCase(), mutable);
120+
}
121+
else {
122+
this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
123+
}
111124
}
112125

113126
@Override
@@ -154,6 +167,9 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx
154167
if (user == null) {
155168
throw new UsernameNotFoundException(username);
156169
}
170+
if (user instanceof CredentialsContainer) {
171+
return user;
172+
}
157173
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
158174
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
159175
}

core/src/test/java/org/springframework/security/provisioning/InMemoryUserDetailsManagerTests.java

+70
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package org.springframework.security.provisioning;
1818

19+
import java.util.Collection;
1920
import java.util.Properties;
2021

2122
import org.junit.jupiter.api.Test;
2223

2324
import org.springframework.security.authentication.TestAuthentication;
2425
import org.springframework.security.core.Authentication;
26+
import org.springframework.security.core.CredentialsContainer;
27+
import org.springframework.security.core.GrantedAuthority;
2528
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2629
import org.springframework.security.core.context.SecurityContextImpl;
2730
import org.springframework.security.core.userdetails.PasswordEncodedUser;
@@ -105,6 +108,20 @@ public void createUserWhenUserAlreadyExistsThenException() {
105108
.withMessage("user should not exist");
106109
}
107110

111+
@Test
112+
public void createUserWhenInstanceOfMutableUserDetailsThenChangePasswordWorks() {
113+
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
114+
CustomUser user = new CustomUser(User.withUserDetails(PasswordEncodedUser.user()).build());
115+
Authentication authentication = TestAuthentication.authenticated(user);
116+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
117+
given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
118+
manager.setSecurityContextHolderStrategy(strategy);
119+
manager.createUser(user);
120+
String newPassword = "newPassword";
121+
manager.changePassword(user.getPassword(), newPassword);
122+
assertThat(manager.loadUserByUsername(user.getUsername()).getPassword()).isEqualTo(newPassword);
123+
}
124+
108125
@Test
109126
public void updateUserWhenUserDoesNotExistThenException() {
110127
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
@@ -119,4 +136,57 @@ public void loadUserByUsernameWhenUserNullThenException() {
119136
.isThrownBy(() -> manager.loadUserByUsername(this.user.getUsername()));
120137
}
121138

139+
@Test
140+
public void loadUserByUsernameWhenNotInstanceOfCredentialsContainerThenReturnInstanceOfCredentialsContainer() {
141+
MutableUser user = new MutableUser(User.withUserDetails(PasswordEncodedUser.user()).build());
142+
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(user);
143+
assertThat(user).isNotInstanceOf(CredentialsContainer.class);
144+
assertThat(manager.loadUserByUsername(user.getUsername())).isInstanceOf(CredentialsContainer.class);
145+
}
146+
147+
@Test
148+
public void loadUserByUsernameWhenInstanceOfCredentialsContainerThenReturnInstance() {
149+
CustomUser user = new CustomUser(User.withUserDetails(PasswordEncodedUser.user()).build());
150+
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(user);
151+
assertThat(manager.loadUserByUsername(user.getUsername())).isSameAs(user);
152+
}
153+
154+
static class CustomUser implements MutableUserDetails, CredentialsContainer {
155+
156+
private final UserDetails delegate;
157+
158+
private String password;
159+
160+
CustomUser(UserDetails user) {
161+
this.delegate = user;
162+
this.password = user.getPassword();
163+
}
164+
165+
@Override
166+
public Collection<? extends GrantedAuthority> getAuthorities() {
167+
return this.delegate.getAuthorities();
168+
}
169+
170+
@Override
171+
public String getPassword() {
172+
return this.password;
173+
}
174+
175+
@Override
176+
public void setPassword(final String password) {
177+
this.password = password;
178+
}
179+
180+
@Override
181+
public String getUsername() {
182+
return this.delegate.getUsername();
183+
}
184+
185+
@Override
186+
public void eraseCredentials() {
187+
this.password = null;
188+
}
189+
190+
}
191+
122192
}

0 commit comments

Comments
 (0)