Je migre de Spring Boot 1.4.9 vers Spring Boot 2.0 et également vers Spring Security 5 et j'essaie de m'authentifier via OAuth 2. Mais je reçois le message d'erreur suivant:
Java.lang.IllegalArgumentException: il n'y a pas de PasswordEncoder mappé pour l'id "null
De la documentation de Spring Security 5 , je découvre que le format de stockage du mot de passe est modifié.
Dans mon code actuel, j'ai créé mon bean encoder de mot de passe en tant que:
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Cependant il me donnait l'erreur ci-dessous:
Le mot de passe codé ne ressemble pas à BCrypt
Je mets donc à jour le codeur comme indiqué dans le document Spring Security 5 :
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Maintenant, si je peux voir le mot de passe dans la base de données, il le stocke comme
{bcrypt}$2a$10$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ
Avec cette première erreur disparue et maintenant, lorsque j'essaie de faire de l'authentification, j'obtiens une erreur:
Java.lang.IllegalArgumentException: il n'y a pas de PasswordEncoder mappé pour l'id "null
Pour résoudre ce problème, j'ai essayé toutes les questions ci-dessous de Stackoverflow:
Voici une question similaire à la mienne mais pas répondue:
REMARQUE: Je suis déjà en train de stocker un mot de passe crypté dans la base de données, il n'est donc pas nécessaire de recoder dans UserDetailsService
.
Dans la documentation Spring security 5 , ils ont suggéré que vous puissiez gérer cette exception en utilisant:
DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches (PasswordEncoder)
Si tel est le correctif alors où devrais-je le mettre? J'ai essayé de le mettre dans PasswordEncoder
haricot comme ci-dessous mais cela ne fonctionnait pas:
DelegatingPasswordEncoder def = new DelegatingPasswordEncoder(idForEncode, encoders);
def.setDefaultPasswordEncoderForMatches(passwordEncoder);
MyWebSecurity class
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(HttpMethod.OPTIONS)
.antMatchers("/api/user/add");
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Configuration MyOauth2
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
@Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("test")
.scopes("read", "write")
.authorities(Roles.ADMIN.name(), Roles.USER.name())
.authorizedGrantTypes("password", "refresh_token")
.secret("secret")
.accessTokenValiditySeconds(1800);
}
}
Veuillez me guider avec ce problème. J'ai passé des heures à résoudre ce problème, mais pas en mesure de le réparer.
Lorsque vous configurez le ClientDetailsServiceConfigurer
, vous devez également appliquer le nouveau format de stockage du mot de passe au secret client.
.secret("{noop}secret")
Pour les personnes confrontées au même problème et n'ayant pas besoin d'une solution sécurisée - pour les tests et le débogage principalement -, les utilisateurs en mémoire peuvent toujours être configurés.
Ceci est juste pour jouer - pas de scénario du monde réel.
L'approche utilisée ci-dessous est obsolète.
C'est de là que je l'ai eu:
Dans votre WebSecurityConfigurerAdapter
ajoutez ce qui suit:
@SuppressWarnings("deprecation")
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
Ici, évidemment, les mots de passe sont hachés, mais restent disponibles en mémoire.
Bien sûr, vous pouvez aussi utiliser une vraie PasswordEncoder
comme BCryptPasswordEncoder
et préfixer le mot de passe avec le bon identifiant:
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Ajoutez .password("{noop}password")
au fichier de configuration de la sécurité.
Par exemple :
auth.inMemoryAuthentication()
.withUser("admin").roles("ADMIN").password("{noop}password");
Chaque fois que Spring stocke le mot de passe, il insère un préfixe d'encodeur dans les mots de passe encodés tels que bcrypt, scrypt, pbkdf2, etc. s'il n'y a pas de préfixe dans le mot de passe codé, il utilise defaultPasswordEncoderForMatches. Vous pouvez afficher la méthode des correspondances de DelegatingPasswordEncoder.class pour voir comment elle fonctionne. Nous avons donc besoin de définir defaultPasswordEncoderForMatches avec les lignes suivantes.
@Bean(name="myPasswordEncoder")
public PasswordEncoder getPasswordEncoder() {
DelegatingPasswordEncoder delPasswordEncoder= (DelegatingPasswordEncoder)PasswordEncoderFactories.createDelegatingPasswordEncoder();
BCryptPasswordEncoder bcryptPasswordEncoder =new BCryptPasswordEncoder();
delPasswordEncoder.setDefaultPasswordEncoderForMatches(bcryptPasswordEncoder);
return delPasswordEncoder;
}
Maintenant, vous devrez peut-être également fournir à votre fournisseur d'authentification cet encodeur avec DefaultPasswordEncoderForMatches. Je l'ai fait avec les lignes ci-dessous dans mes classes de configuration.
@Bean
@Autowired
public DaoAuthenticationProvider getDaoAuthenticationProvider(@Qualifier("myPasswordEncoder") PasswordEncoder passwordEncoder, UserDetailsService userDetailsServiceJDBC) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsServiceJDBC);
return daoAuthenticationProvider;
}
En ce qui concerne
Le mot de passe encodé ne ressemble pas à BCrypt
Dans mon cas, il y avait une incompatibilité dans BCryptPasswordEncoder utilisé par le constructeur par défaut (10), car pwd hash a été généré avec la force 4. J'ai donc défini la force explicitement.
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
ma version de Spring Security est également 5.1.6 et fonctionne parfaitement avec BCryptPasswordEncoder