Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
311 views
in Technique[技术] by (71.8m points)

java - Bad credentials though valid on a spring /authenticate token generator endpoint

I'm trying to build a route, processed in Spring, to generate a token if the user/password submited in the body are valid. Only in that scenario it responds with the token.

The problem is that I'm Posting the correct username and password, exactly as stored in DB but I keep getting a "bad credentials" error. This is the controller:

@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
    try {
        LOGGER.info("Received a request to generate a token for user: "+authenticationRequest.getUsername()+"/"+authenticationRequest.getPassword());

        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        System.out.println(userDetails);
        // Logs: org.springframework.security.core.userdetails.User@7ee29d27: Username: thisWasTheGoodUserName; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Admin
        // And also logs the hibernate query:
        //    select
        //        employee0_.id as id1_0_,
        //        employee0_.employee_name as employee_name2_0_,
        //        employee0_.pwd as pwd3_0_,
        //        employee0_.user_name as user_nam4_0_ 
        //    from
        //        employees employee0_ 
        //    where
        //        employee0_.user_name=?

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, authenticationRequest.getPassword(), userDetails.getAuthorities());
        // This step gets executed

        authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        // And the same query to the DB is logged, one that I can run on dbeaver manually and get results from:
        //    select
        //        employee0_.id as id1_0_,
        //        employee0_.employee_name as employee_name2_0_,
        //        employee0_.pwd as pwd3_0_,
        //        employee0_.user_name as user_nam4_0_ 
        //    from
        //        employees employee0_ 
        //    where
        //        employee0_.user_name=?
        // And throws exception in this authenticate
        
        final String token = jwtTokenUtil.generateToken(userDetails);

        TokenSucess tokenSuccessResponse = new TokenSucess(true, new JwtResponse(token));
        LOGGER.info("Obtained token with success ");
        return new ResponseEntity<TokenSucess>(tokenSuccessResponse, HttpStatus.OK);
    } catch (Exception e) {
        TokenError tokenErrorResp = new TokenError(false, "Error generating token.");
        LOGGER.error("Error generating a token. Details: "+ e);
        return new ResponseEntity<TokenError>(tokenErrorResp, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

This is the method I'm using in the service:

@Service
public class JwtUserDetailsService implements UserDetailsService {
    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Employee emp = employeeRepository.findByUserName(username);
        if (emp == null ) {
            throw new UsernameNotFoundException("Employee not found with username: " + username);
        }
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("Admin"));

        return new org.springframework.security.core.userdetails.User(emp.getUserName(), emp.getPassword(), authorities);
    }
}

The security config:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);

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

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

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        try {
            httpSecurity.csrf().disable()
                    // don't authenticate this particular request
                    .authorizeRequests().antMatchers("/authenticate").permitAll().
                    // all other requests need to be authenticated
                            anyRequest().authenticated().and().
                            exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

            // Add a filter to validate the tokens with every request
            httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        } catch (Exception e) {
            LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
        }
    }
}

And this is the curl example:

curl --location --request POST 'http://127.0.0.1:<port>/authenticate' 
--header 'Content-Type: application/json' 
--data-raw '{
    "username": "thisWasTheGoodUserName",
    "password": "123454321"
}'

Already tried with diferent scenarios in DB:

1. thisWasTheGoodUserName/123454321
2. thisWasTheGoodUserName/$2y$12$Mj0PRHipe14Wgm5c/GOuO.RyhjhuwRwoQYUnK8LcgsvHzQ4weYHGm (bcrypted 123454321)

Using several "step into" I found that the problem is happening in the following function - /Users/<user>/.m2/repository/org/springframework/security/spring-security-core/5.0.3.RELEASE/spring-security-core-5.0.3.RELEASE.jar!/org/springframework/security/authentication/dao/DaoAuthenticationProvider.class, even though the passwords are se same string:

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Authentication failed: no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        String presentedPassword = authentication.getCredentials().toString();

        // presentedPassword is exactly the same as the one in userDetails.getPassword() but the matcher returns False...

        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

Any idea what I could be doing wrong and causing the following Exception?

ERROR c.r.t.s.c.JwtAuthenticationController - Error generating a token. Details: org.springframework.security.authentication.BadCredentialsException: Bad credentials

Note: found the problem causing all this: the stored password in the DB was encrypted using an online bcrypt generator... The value does not match the generated bcrypt encoded password even though they are the same string.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I mean you can do a customized authenticate via AuthenticationProvider. You should comment configureGlobal(). The below is sample of WebSecurityConfig.



@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

   // @Autowired
   // private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);

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

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

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private EmployeeRepository employeeRepository;

    @Bean
    protected AuthenticationProvider authenticationProvider(){


        return new AuthenticationProvider(){

            @Override
            public Authentication authenticate(Authentication auth) throws AuthenticationException {

            
                String username= request.getParameter("username");
                String password = request.getParameter("password");


                Employee user= employeeRepository.findByUserName(username);
                if (emp == null ) {
                  throw new UsernameNotFoundException("Employee not found with username: " 
                  + username);
                }

                // validate pwd by yourself. 
                // you should use same bcrypt generator to validate when saving pwd in DB
                if(!UserUtils.validatePassword(password, user.getPassword())){
                    throw new BadCredentialsException("wrong password!");
                }
                

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                authorities.add(new SimpleGrantedAuthority("Admin"));               

                return new UsernamePasswordAuthenticationToken(username,
                        null, authorities);

            }

            @Override
            public boolean supports(Class<?> authentication) {
                return true;
            }

        };
    }



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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        try {
            httpSecurity.csrf().disable()
                    // don't authenticate this particular request
                    .authorizeRequests().antMatchers("/authenticate").permitAll().
                    // all other requests need to be authenticated
                            anyRequest().authenticated().and().
                            exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

            // Add a filter to validate the tokens with every request
            httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        } catch (Exception e) {
            LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
        }
    }
}



与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...