Spring Security BadCredentialsException

问题描述 投票:0回答:4

我正在关注 这个 Baeldung 教程,我看不到任何差异(除了预先生成的登录页面模板),但在尝试使用硬编码的用户/密码组合登录时,我仍然得到

BadCredentialsException
,我已经确认它在数据库中,并且其中的密码已加密。

这是我的代码,如果需要更多,请告诉我:

安全配置:

@Component
@EnableWebSecurity
@SuppressWarnings("unused")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/css/**").permitAll()
                .antMatchers("/error").permitAll()
                .antMatchers("/action/**").hasRole("USER")
                .antMatchers("/action_template/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/login").failureUrl("/login-error");
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }

    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
    }

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

}

我的用户详细信息服务:

@Service
@Transactional
@SuppressWarnings("unused")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

        User user = userRepository.findByEmail(email);

        if (user == null) {
            throw new UsernameNotFoundException(
                    "No user found with username: " + email);
        }

        final List<GrantedAuthority> authorities = new ArrayList<>();
        user.getRoles().forEach((r) -> authorities.add(new SimpleGrantedAuthority(r)));

        return  new org.springframework.security.core.userdetails.User(
                user.getEmail(),
                user.getPassword().toLowerCase(),
                true,
                true,
                true,
                true,
                authorities
        );
    }

}

编辑:

这是堆栈跟踪

2018-11-14 13:52:05.785 DEBUG 2100 --- [nio-8090-exec-5] w.a.UsernamePasswordAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials

org.springframework.security.authentication.BadCredentialsException: Bad credentials
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:93)
    at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199)
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:124)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

我还编辑了我的SecurityConfig并添加了

.antMatchers("/error").permitAll()
,但没有帮助

编辑2:

这是 Github 存储库,它应该提供更多见解。 我不知道如何进一步诊断......

java spring spring-mvc spring-boot spring-security
4个回答
2
投票

Spring Boot 可以自动拾取/创建所有必需的 bean,因此您可以像这样简化 SecurityConfig(它会找到您的 UserDetailsService 并在没有定义此类 bean 的情况下创建默认的 DaoAuthenticationProvider):

@Component
@EnableWebSecurity
@SuppressWarnings("unused")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/css/**").permitAll()
                .antMatchers("/error").permitAll()
                .antMatchers("/action/**").hasRole("USER")
                .antMatchers("/action_template/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/login").failureUrl("/login-error");
    }

}

还在不同的配置类中定义passwordEncoder bean(但是我不确定是否还需要它):

@Configuration
public class BeanConfiguration {

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

}

1
投票

我……很困惑。 我已经找到问题了我不知道为什么我认为保留它是可以的,我也不知道为什么教程在那里有它,但即使密码是“password”,

user.getPassword().toLowerCase()
是小写的 BCrypt 哈希,而不是实际的密码。 删除
.toLowerCase()
即可解决问题。


0
投票

问题看起来与您注册密码编码的方式有关。尝试这样注册:

@Autowired
public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

     auth
          .userDetailsService(userDetailsService)
          .passwordEncoder(bCryptPasswordEncoder);
}

0
投票

我正在为那些使用 Spring Security 6 并使用身份验证管理器遇到错误凭据错误的人编写此答案。我的登录方法出现错误:

@PostMapping("/login")
public ResponseEntity<AuthResponseDto> login(@RequestBody LoginDto loginDto) {
    logger.info("Login request: {}", loginDto);
    try {
        UserDetails userDetails = userService.loadUserByUsername(loginDto.getUsername());
        logger.info("User details: {}", userDetails);

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        userDetails.getUsername(),
                        userDetails.getPassword(),
                        userDetails.getAuthorities()));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String token = jwtGenerator.generateToken(userDetails);
        logger.info("Login successful");
        return new ResponseEntity<>(new AuthResponseDto(token), HttpStatus.OK);

    } catch (Exception e) {
        logger.error(null, e);
        return new ResponseEntity<>(new AuthResponseDto(""), HttpStatus.UNAUTHORIZED);
    }
}

我收到了错误的凭据错误,我花了两天时间才弄清楚我的错误。

我没有将从用户通过loginDto输入的原始密码传递给authenticate方法,而是传递从数据库检索到的编码密码,因此DaoAuthenticationProvider无法将密码与密码编码器匹配。

为了解决这个问题,我更改了传递给身份验证的数据,如下所示:

            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginDto.getUsername(),
                        loginDto.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);

此外,您不需要从数据库检索用户详细信息,因为身份验证模块会为您完成此操作!

所以完整的登录控制器现在是这样的:

@PostMapping("/login")
public ResponseEntity<AuthResponseDto> login(@RequestBody LoginDto loginDto) {
    logger.info("Login request: {}", loginDto);
    try {

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginDto.getUsername(),
                        loginDto.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String token = jwtGenerator.generateToken(authentication);
        logger.info("Login successful");
        return new ResponseEntity<>(new AuthResponseDto(token), HttpStatus.OK);

    } catch (Exception e) {
        logger.error(null, e);
        return new ResponseEntity<>(new AuthResponseDto(""), HttpStatus.UNAUTHORIZED);
    }
}

我的安全配置是这样的(我仍然需要实现一个过滤器来对已经拥有 JWT 令牌的用户进行身份验证):

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private AuthEntryPoint authEntryPoint;

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(
                        authorizeRequests -> authorizeRequests
                                .requestMatchers("/api/v1/auth/**")
                                .permitAll()
                                .anyRequest()
                                .authenticated())
                .exceptionHandling(
                        exceptionHandling -> exceptionHandling.authenticationEntryPoint(authEntryPoint))
                .sessionManagement(
                        session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();
    }

    @Bean
    AuthenticationManager authenticationManager(
            AuthenticationConfiguration authenticationConfiguration) throws Exception {

        return authenticationConfiguration.getAuthenticationManager();
    }

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

    @Bean
    UserService userService() {
        return new UserService();
    }
}

如果您对我的 JWT 令牌生成器感到好奇,请访问:

@Component
public class JWTGenerator {
  private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);

  public String generateToken(Authentication authentication) {
    String username = authentication.getName();
    Date currentDate = new Date();
    Date expireDate = new Date(currentDate.getTime() + SecurityConstants.EXPIRATION_TIME);

    String token = Jwts.builder()
        .setSubject(username)
        .setIssuedAt(new Date())
        .setExpiration(expireDate)
        .signWith(key, SignatureAlgorithm.HS512)
        .compact();
    System.out.println("New token :");
    System.out.println(token);
    return token;
  }

  public String getUsernameFromJWT(String token) {
    Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    return claims.getSubject();
  }

  public boolean validateToken(String token) {
    try {
      Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
      return true;
    } catch (Exception ex) {
      throw new AuthenticationCredentialsNotFoundException(
          "JWT was expired or incorrect", ex.fillInStackTrace());
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.