49

I am using Spring Boot, Spring Security, OAuth2 and JWT to authenticate my application, but I keep getting this nasty error and I don't have any idea what is wrong. My CustomDetailsService class:

@Service
public class CustomDetailsService implements UserDetailsService {

    private static final Logger logger = LoggerFactory.getLogger(CustomDetailsService.class);

    @Autowired
    private UserBO userBo;

    @Autowired
    private RoleBO roleBo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AppUsers appUsers = null;
        try {
            appUsers = this.userBo.loadUserByUsername(username);
            System.out.println("========|||=========== "+appUsers.getUsername());
        }catch(IndexOutOfBoundsException e){
            throw new UsernameNotFoundException("Wrong username");
        }catch(DataAccessException e){
            e.printStackTrace();
            throw new UsernameNotFoundException("Database Error");
        }catch(Exception e){
            e.printStackTrace();
            throw new UsernameNotFoundException("Unknown Error");
        }

        if(appUsers == null){
            throw new UsernameNotFoundException("Bad credentials");
        }
        logger.info("Username: "+appUsers.getUsername());
        return buildUserFromUserEntity(appUsers);
    }

    private User buildUserFromUserEntity(AppUsers authUsers) {
        Set<UserRole> userRoles = authUsers.getUserRoles();

        boolean enabled = true;
        boolean accountNotExpired = true;
        boolean credentialsNotExpired = true;
        boolean accountNotLocked = true;

        if (authUsers.getAccountIsActive()) {
            try {
                if(authUsers.getAccountExpired()){
                    accountNotExpired = true;
                } else if (authUsers.getAccountIsLocked()) {
                    accountNotLocked = true;
                } else {
                    if (containsRole((userRoles), roleBo.findRoleByName("FLEX_ADMIN"))){
                        accountNotLocked = false;
                    }
                }
            }catch(Exception e){
                enabled = false;
                e.printStackTrace();
            }
        }else {
            accountNotExpired = false;
        }
        // convert model user to spring security user
        String username = authUsers.getUsername();
        String password = authUsers.getPassword();

        List<GrantedAuthority> authorities = buildUserAuthority(userRoles);

        User springUser = new User(username, password,enabled, accountNotExpired, credentialsNotExpired, accountNotLocked, authorities);
        return springUser;
    }
}

OAuth2Config:

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public JwtAccessTokenConverter tokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey(PRIVATE_KEY);
        tokenConverter.setVerifierKey(PUBLIC_KEY);
        return tokenConverter;
    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(tokenConverter());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpointsConfigurer) throws Exception {
        endpointsConfigurer.authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .accessTokenConverter(tokenConverter());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
        securityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient(CLIENT_ID)
                .secret(CLIENT_SECRET)
                .scopes("read","write")
                .authorizedGrantTypes("password","refresh_token")
                .accessTokenValiditySeconds(20000)
                .refreshTokenValiditySeconds(20000);
    }
}

SecurityConfig:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomDetailsService customDetailsService;

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Autowired
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(customDetailsService).passwordEncoder(encoder());
        System.out.println("Done...finito");
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.NEVER);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManagerBean();
    }
}

No error message except:

Hibernate: select appusers0_.id as id1_2_, appusers0_.account_expired as account_2_2_, appusers0_.account_is_active as account_3_2_, appusers0_.account_is_locked as account_4_2_, appusers0_.bank_acct as bank_acc5_2_, appusers0_.branch_id as branch_i6_2_, appusers0_.bvn as bvn7_2_, appusers0_.create_date as create_d8_2_, appusers0_.created_by as created_9_2_, appusers0_.email as email10_2_, appusers0_.email_verified_code as email_v11_2_, appusers0_.gender as gender12_2_, appusers0_.gravatar_url as gravata13_2_, appusers0_.is_deleted as is_dele14_2_, appusers0_.lastname as lastnam15_2_, appusers0_.middlename as middlen16_2_, appusers0_.modified_by as modifie17_2_, appusers0_.modified_date as modifie18_2_, appusers0_.orgnization_id as orgniza19_2_, appusers0_.password as passwor20_2_, appusers0_.phone_no as phone_n21_2_, appusers0_.surname as surname22_2_, appusers0_.token_expired as token_e23_2_, appusers0_.username as usernam24_2_ from users appusers0_ where appusers0_.username=?
Tinubu
2018-03-31 01:42:03.255  INFO 4088 --- [nio-8072-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-03-31 01:42:03.255  INFO 4088 --- [nio-8072-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-03-31 01:42:03.281  INFO 4088 --- [nio-8072-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms
2018-03-31 01:42:03.489  WARN 4088 --- [nio-8072-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder     : Encoded password does not look like BCrypt

My entity model classes are:

@Entity
@Table(name="USERS")
@DynamicUpdate
public class AppUsers {

    @Id
    @Column(name="ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ApiModelProperty(notes = "The user auto generated identity", required = true)
    private Long id;

    @Column(name="username")
    @ApiModelProperty(notes = "The username parameter", required = true)
    private String username;

    @Column(name="password")
    @ApiModelProperty(notes = "The password parameter", required = true)
    private String password;

    @JsonManagedReference
    @OneToMany(mappedBy="appUsers")
    private Set<UserRole> userRoles;

'''''' setters and getters
}

Role entity:

@Entity
@Table(name="ROLE")
public class Role {

    @javax.persistence.Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id", unique = true, nullable = false)
    private Long Id;

    @Column(name = "name")
    private String roleName;

   @JsonManagedReference
    @OneToMany(mappedBy="role")
    private Set<UserRole> userRoles;

   //getters and setters

}

UserRole entity:

@Entity
@Table(name="USER_ROLE")
@DynamicUpdate
public class UserRole   implements Serializable {

    private static final long serialVersionUID = 6128016096756071383L;

    @Id
    @Column(name="ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ApiModelProperty(notes = "The userrole auto generated identity", required = true)
    private long id;

    @JsonBackReference
    @ManyToOne//(fetch=FetchType.LAZY)
    private AppUsers appUsers;

    @JsonBackReference
    @ManyToOne//(fetch=FetchType.LAZY)
    private Role role;

   // getters and setters
}

My password in the database is properly encrypted Spring security BCrypt and it datatype is varchar(255) which is larger than 60.

Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Kunle Ajiboye
  • 723
  • 1
  • 8
  • 22
  • @Min Hyoung Hong.. The code ran well until I need to pass the authentication credentials. the only error it throws is: (Encoded password does not look like BCrypt) – Kunle Ajiboye Mar 31 '18 at 01:18

27 Answers27

57

BCryptPasswordEncoder shows this warning when it fails to match a raw password with an encoded password.

The hashed password might be $2b or $2y now.

And there is a bug in Spring Security that has a regex always looking for $2a. Put a debug point at the matches() method in the BCryptPasswordEncoder.class.

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
Nikhil
  • 1,021
  • 12
  • 13
  • 8
    Im running into this problem right now. I've hashed passwords with online tools, and if hashed password starts with $2y or $2b i got 'Encoded password does not look like BCrypt'. The only way i found to avoid this error is having hashed passwords that starts with $2a – Lucas. D Oct 07 '18 at 17:49
  • 4
    FYI The Bug was tracked on GitHub: https://github.com/spring-projects/spring-security/issues/3320. It seems like a PR was merged in October 2018 (3 Years after Bug Report and 4 Years after BCrypt update). It will probably be released with a new Spring Security Version (5.2.0) – rwenz3l Dec 11 '18 at 08:48
  • Spring-core-security version 5.0.11, class BCryptPasswordEncoder, method "matches" using "private Pattern BCRYPT_PATTERN = Pattern .compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");" will be fall in block: if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) { logger.warn("Encoded password does not look like BCrypt"); return false; } – Do Tat Hoan Jul 04 '19 at 08:55
  • 1
    for me culprit was using char[] for storing password – anshulkatta Oct 16 '21 at 07:25
  • For people coming from any tutorial, the password field in DB should be longer in your table. Set it to varchar(100) and see if it works. – piepi Dec 25 '21 at 18:40
39

Can you double check your client secret is encoded?

@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
    configurer
            .inMemory()
            .withClient(clientId)
            .secret(passwordEncoder.encode(clientSecret))
            .authorizedGrantTypes(grantType)
            .scopes(scopeRead, scopeWrite)
            .resourceIds(resourceIds);
}
16

The PasswordEncoder should be set like this:

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Pascal Joret
  • 161
  • 1
  • 5
  • 3
    You're right.. but this leads to another issue `java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"` – PAA Jan 01 '19 at 15:28
  • You can bCrypt client secret like {bcrypt}$2a...... Hope fully it's work! – amran_bd Jan 31 '19 at 09:52
  • @Pra_A how did you go around this ```java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"``` am getting the same error – Erycoking Nov 29 '21 at 05:44
12

When oauth2 dependecncies moved to cloud, I started facing this issue. Earlier it was part of security framework :

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

Now it is part of cloud framework :

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

So if you are using cloud dependency (Finchley.RELEASE) then you may need to encode the secret like below :

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients
            .inMemory()
            .withClient("clientapp")
            .authorizedGrantTypes("password","refresh_token")
            .authorities("USER")
            .scopes("read", "write")
            .resourceIds(RESOURCE_ID)
            .secret(passwordEncoder.encode("SECRET"));
}
Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
PKumar
  • 516
  • 9
  • 20
8

As of today, with Spring Boot 2.1.7.RELEASE, I am still experiencing this issue. I was using some online tools which gave me hashes starting with $2b or $2y, which Spring's BCryptPasswordEncoder does not allow:

public class BCryptPasswordEncoder implements PasswordEncoder {
    private Pattern BCRYPT_PATTERN = Pattern
            .compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
...

Solution: use BCryptPasswordEncoder class to encode the password:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("admin"));

And then:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
        throws Exception {
    auth.inMemoryAuthentication()
            .withUser("admin")
            .password("{bcrypt}$2a$10$6CW1agMzVzBhxDzK0PcxrO/cQcmN9h8ZriVEPy.6DJbVeyATG5mWe")
            .roles("ADMIN");
}
Jan Bodnar
  • 10,969
  • 6
  • 68
  • 77
6

Update field client_secret in table oauth_client_details with BCryptPasswordEncoder in case you migrate spring boot from 1x to 2x. To encode secret use:

    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String password = "12345678";
    String encodedPassword = passwordEncoder.encode(password);

    System.out.println();
    System.out.println("Password is         : " + password);
    System.out.println("Encoded Password is : " + encodedPassword);
    System.out.println();

    boolean isPasswordMatch = passwordEncoder.matches(password, encodedPassword);
    System.out.println("Password : " + password + "   isPasswordMatch    : " + isPasswordMatch);
Makara Set
  • 333
  • 3
  • 4
6

For my own similar scenario I just encoded the password like this passwordEncoder().encode("password") instead of raw String "password":

authenticationManagerBuilder
  .inMemoryAuthentication()
  .withUser("user")
  // Just changed here
  .password(passwordEncoder().encode("password"))
  .roles("USER");


@Bean
public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
}
Jehad Nasser
  • 3,227
  • 5
  • 27
  • 39
5

The best way to identify this problem "Encoded password does not look like BCrypt" is setup a break porint in class org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder. And then check the root cause for the warnning.

if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
    logger.warn("Encoded password does not look like BCrypt");
    return false;
}
Lin Chen
  • 173
  • 1
  • 3
  • 14
  • 1
    For my error message "Encoded password does not look like BCrypt" . The length of encode password is 60 byte. But the column password in database is 100. So I have change the column password from char to varchar to fix this issue. – Lin Chen Mar 23 '19 at 03:00
  • my database is db2 on iseries. – Lin Chen Mar 23 '19 at 03:05
3

BCryptPasswordEncoder does not strip the {bcrypt} id, but DelegatingPasswordEncoder do it. When I define explicitly BCryptPasswordEncoder as an encoder for DaoAuthenticationProvider it calls matches method on BCryptPasswordEncoder (without id strip), but not on DelegatingPasswordEncoder (with id strip).

Zagrebin Victor
  • 163
  • 1
  • 6
2

I had the same error and it was because of the datatype of the password column, this column was length blank fixed (CHARACTER), so make sure You're using a VARCHAR datatype or else change the length to 60 for you password column.

2

Please check if your method UserDetails loadUserByUsername(String username) returns valid UserDetail object. If Returned object is null / object with invalid values then also you will see this error.

sandeep pandey
  • 350
  • 1
  • 10
2

In Spring Security 5, the default encoder is DelegatingPasswordEncoder, which required Password Storage Format.

Read this

    private PasswordEncoder delegateEncoder =
            PasswordEncoderFactories.createDelegatingPasswordEncoder();

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
            clients
                    .jdbc(dataSource)
                    .passwordEncoder(delegateEncoder);
    }

Generate the password or secret code using default encoder which is DelegatingPasswordEncoder

System.out.println(delegateEncoder.encode("123123"));
// it generates the encoded code something like this: 
// {bcrypt}$2a$10$0aISzamI0jBCVTxONzJlHOk7O7QS.XPFIheLVhXultVa9Ju7SarZ6
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
2
  1. In case you've added "{bcrypt}" right before password, and not configured your app accordingly, you will get this error. Consider removing this.
  2. If still not resolved, just set the column length of the user's password to 60( 68 if you've added {bcryt} at the beginning) Worked for me!
  • I also stumbled over this problem. I finally found out that my field in the DB was too long so that the PW returned from the DB bad trailing spaces, preventing a successful PW match. – hage Feb 04 '22 at 09:14
  • I used the Spring CLI to generated a bcrypt password and stripping the {bcrypt} from thtat made the generated Spring login flow work. – Kasper Aug 22 '23 at 03:58
2

I had same issue and problem is that you should encode your inMemory password because when you click login form Spring Security take the extracted password from the HTTP Basic Auth header, hash it automatically and compare it with the hashed password from your UserDetails object. If both match, the user is successfully authenticated otherwise you will get this error.

For example ,

InMemory Password = "123"
passwordEncoder.encode("123")

Reference : link

1

I struggled with this error while doing a Spring Security Course.

My problem was that even though in the AuthenticationManager I was using the encoding, e.g:

    @Autowired
    public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

The Problem was that I was not Encoding the Password when I was saving the Users!! Example:

final Principal entity = new Principal(loginName, passwordEncoder.encode(pass), roles);
razvang
  • 1,055
  • 11
  • 15
0

You are likely missing this bean in your Security configuration SecurityConfig

@Bean
public DaoAuthenticationProvider getAuthenticationProvider() {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(customDetailsService);
    authenticationProvider.setPasswordEncoder(encoder());
    return authenticationProvider;
}
bitscanbyte
  • 650
  • 8
  • 14
  • The `UserDetaulsService` and the `PasswordEncoder` are already defined at `protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder)`. Isn't that enough? – misabelcarde Apr 01 '18 at 18:12
0

use noop in secret for tests.

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("angular")
            .secret("{noop}@ngular0")
            .scopes("read", "write")
            .authorizedGrantTypes("password")
            .accessTokenValiditySeconds(1800);
}
Faqtt0
  • 125
  • 1
  • 3
  • 16
0

I got rid of this error message by using a long, 60 chars+ password e.g. CXPW3XT2vXwBZk9mYZ5eCrKPM8kXJC6bbwJQjtGq2NQRYQPzsvqTwYz8JvWhWD5KLrrUHHammBNV3tkkyA4U

mikelus
  • 927
  • 11
  • 25
0

The UserDetails entity has to return the encoded password. This varies depending on the implementation.

class MyUserDetails: UserDetails {

    //...

    override fun getPassword(): String {
        return passwordEncoder().encode("aPassword")
    }

    private fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }

}

GL

Braian Coronel
  • 22,105
  • 4
  • 57
  • 62
0

The problem is the empty spaces in password.

Try to trim your password while returning from your custom UserDetailsService class.

0

I changed my bcrypt hash which started from $2y to $2a i.e. $2y$12$0Evlz//oO to $2a$12$0Evlz//oO and it worked for me.

Hafiz Hamza
  • 311
  • 3
  • 16
0

I have tried all the solutions above, considering my usecase was different than this one in Spring Boot. In my case, I switched my DBMS from MySQL to PostgreSQL, and the app was working fine in case of MySQL, with login/logout feature working perfectly. But when I switched to Postgres, this error started showing in login functionality. I noticed in pgAdmin , that BCrypt passwords had extra spaces in the end, I even tried to manually remove those spaces, but that too rolled back the changes on its own every time.

Then finally I read somewhere that, BCrypt encrypted text length is fixed i.e. 60 chars. But my password field in Postgres was of 80 chars. I simply changed the field length from 80 to 60 chars. You can either do it using GUI, or from CLI, using ALTER TABLE command, whatever suits you.

Divya Gupta
  • 494
  • 8
  • 24
0

With the following code I get rid of this error

 @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
        
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encodedPassword = passwordEncoder.encode(CLIENT_SECRET);

        configurer
                .inMemory()
                .withClient(CLIENT_ID)
                .secret(encodedPassword)
                .authorizedGrantTypes(GRANT_TYPE)
                .scopes(SCOPE_READ, SCOPE_WRITE)
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
                refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
    }

So it is necessary to encode the secret

0

For spring security 5 OAuth 2 Server, this is how I fixed it

    @Bean
    public PasswordEncoder delegatingPasswordEncoder() {
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("bcrypt", encoders);
        passwordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());
        return passwordEncoder;
    }
Erycoking
  • 459
  • 9
  • 15
0

lol spring rrr me try this

 @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }


 @Bean
 public PasswordEncoder passwordEncoder() {
     return NoOpPasswordEncoder.getInstance();
}
0

I was also facing same issue:

In my case table's column length was 50 char only which was causing this issue later I modify column length to 100 as suggested in above and it worked for me.

ALTER TABLE `users`
    CHANGE COLUMN `password` `password` VARCHAR(100) NOT NULL AFTER `enabled`;
Romit
  • 13
  • 5
0

My views bCryptEncoder.matches("rawString",encodedPwdFromDB); This is the actual parameter need to check while passing the values

  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 27 '23 at 14:33