As of Spring Security 3.1.4.RELEASE, the old org.springframework.security.authentication.encoding.PasswordEncoder
has been deprecated in favour of org.springframework.security.crypto.password.PasswordEncoder
. As my application has not been released to the public yet, I decided to move to the new, not deprecated API.
Until now, I had a ReflectionSaltSource
that automatically used the user's registration date as per-user salt for password.
String encodedPassword = passwordEncoder.encodePassword(rawPassword, saltSource.getSalt(user));
During login process, Spring also used my beans to appropriate verify if the user can or can not sign in. I can't achieve this in the new password encoder, because the default implementation of SHA-1 - StandardPasswordEncoder
has only ability to add a global secret salt during the encoder creation.
Is there any reasonable method of how to set it up with the non-deprecated API?
We use the PasswordEncoder that is defined in the Spring Security configuration to encode the password. In this example, the passwords are encoded with the bcrypt algorithm because we set the PasswordEncoder as the password encoder in the configuration. The code just saves the new user to the database.
@Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @GetMapping("/test") public void fillDatabse() { String encodedPw=bCryptPasswordEncoder. encode("test"); Password p = new Password(encodedPw);
Class NoOpPasswordEncoder. Deprecated. This PasswordEncoder is not secure. Instead use an adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder.
If you haven't actually registered any users with your existing format then you would be best to switch to using the BCrypt password encoder instead.
It's a lot less hassle, as you don't have to worry about salt at all - the details are completely encapsulated within the encoder. Using BCrypt is stronger than using a plain hash algorithm and it's also a standard which is compatible with applications using other languages.
There's really no reason to choose any of the other options for a new application.
Here is the implementation of BCrypt which is working for me.
in spring-security.xml
<authentication-manager > <authentication-provider ref="authProvider"></authentication-provider> </authentication-manager> <beans:bean id="authProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <beans:property name="userDetailsService" ref="userDetailsServiceImpl" /> <beans:property name="passwordEncoder" ref="encoder" /> </beans:bean> <!-- For hashing and salting user passwords --> <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
In java class
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String hashedPassword = passwordEncoder.encode(yourpassword);
For more detailed example of spring security Click Here
Hope this will help.
Thanks
I had a similar issue. I needed to keep the legacy encrypted passwords (Base64/SHA-1/Random salt Encoded) as users will not want to change their passwords or re-register. However I wanted to use the BCrypt encoder moving forward too.
My solution was to write a bespoke decoder that checks to see which encryption method was used first before matching (BCrypted ones start with $
).
To get around the salt issue, I pass into the decoder a concatenated String of salt + encrypted password via my modified user object.
Decoder
@Component
public class LegacyEncoder implements PasswordEncoder {
private static final String BCRYP_TYPE = "$";
private static final PasswordEncoder BCRYPT = new BCryptPasswordEncoder();
@Override
public String encode(CharSequence rawPassword) {
return BCRYPT.encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (encodedPassword.startsWith(BCRYP_TYPE)) {
return BCRYPT.matches(rawPassword, encodedPassword);
}
return sha1SaltMatch(rawPassword, encodedPassword);
}
@SneakyThrows
private boolean sha1SaltMatch(CharSequence rawPassword, String encodedPassword) {
String[] saltHash = encodedPassword.split(User.SPLIT_CHAR);
// Legacy code from old system
byte[] b64salt = Base64.getDecoder().decode(saltHash[0].getBytes());
byte[] validHash = Base64.getDecoder().decode(saltHash[1]);
byte[] checkHash = Utility.getHash(5, rawPassword.toString(), b64salt);
return Arrays.equals(checkHash, validHash);
}
}
User Object
public class User implements UserDetails {
public static final String SPLIT_CHAR = ":";
@Id
@Column(name = "user_id", nullable = false)
private Integer userId;
@Column(nullable = false, length = 60)
private String password;
@Column(nullable = true, length = 32)
private String salt;
.
.
@PostLoad
private void init() {
username = emailAddress; //To comply with UserDetails
password = salt == null ? password : salt + SPLIT_CHAR + password;
}
You can also add a hook to re-encode the password in the new BCrypt format and replace it. Thus phasing out the old method.
Having just gone round the internet to read up on this and the options in Spring I'd second Luke's answer, use BCrypt (it's mentioned in the source code at Spring).
The best resource I found to explain why to hash/salt and why use BCrypt is a good choice is here: Salted Password Hashing - Doing it Right.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With