Spring Security JWT获取令牌报错400:invalid grant/Bad credentials求助
我之前排查过不少类似的问题,结合你给出的信息,这个400错误的核心是凭证验证失败,咱们一步步来定位解决:
1. 先确认H2数据库的用户数据是否真的插入成功
你的data.sql里用了user作为表名,但user是H2的保留关键字,直接使用可能导致插入语句静默执行失败,数据库里根本没有用户数据,自然会验证失败。
修正方法:把表名用双引号括起来,修改插入语句:
INSERT INTO "user" (id, username, password) VALUES (1, 'joe.bloggs@example.com', '{bcrypt}$2y$10$rHtfxfURj05SoLAwkn9sMO27B9eS7Js0ceh5pyYmrKoyUk.3VPJGi');
启动项目后可以访问H2控制台(默认路径/h2-console),查询"user"表确认数据是否存在。
2. 检查PasswordEncoder的配置是否正确
你数据库里的密码带了{bcrypt}前缀,这是Spring Security 5+的密码格式标记,用来告诉框架用哪种加密算法验证密码。如果你的Security配置里没有正确配置支持这种格式的PasswordEncoder,就会导致验证失败。
正确的配置示例:
@Bean public PasswordEncoder passwordEncoder() { // 自动识别密码前缀对应的加密算法 return PasswordEncoderFactories.createDelegatingPasswordEncoder(); }
如果你之前用的是new BCryptPasswordEncoder(),它不会识别{bcrypt}前缀,会直接把前缀当成密码的一部分去验证,自然匹配失败。
3. 验证UserDetailsService的实现是否正确
确保你的UserDetailsService在加载用户时,完整返回了数据库里的加密密码,没有做任何额外的处理(比如再次加密)。
正确的实现示例:
@Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); // 直接返回数据库中的加密密码,不要修改 return User.withUsername(user.getUsername()) .password(user.getPassword()) .roles("USER") // 根据你的实际角色配置调整 .build(); } }
这里要注意:绝对不要对从数据库取出的密码再次调用passwordEncoder.encode(),否则会把已经加密的密码再加密一次,导致完全不匹配。
4. 确认获取令牌的请求参数是否正确
获取JWT令牌的POST请求(通常是/oauth/token或自定义端点)需要传递正确的参数:
grant_type:必须是password(如果使用密码模式)username:要和数据库里的joe.bloggs@example.com完全一致(注意大小写、邮箱格式)password:要输入原始明文密码(比如你生成bcrypt密码时用的明文,比如test123),而不是数据库里的加密字符串- 如果配置了客户端认证,还要传递
client_id和client_secret
比如用Postman请求时,表单参数应该是:
grant_type: password username: joe.bloggs@example.com password: 你的原始明文密码 client_id: 你的客户端ID(如果有配置) client_secret: 你的客户端密钥(如果有配置)
5. 检查安全配置中的认证管理器
确保你的Security配置中,AuthenticationManager正确关联了你的UserDetailsService和PasswordEncoder:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder); } // 其他配置...比如JWT过滤器、授权规则等 }
按照上面的步骤逐一排查,应该能快速定位问题。我之前遇到过好几次都是H2表名的问题,或者PasswordEncoder没配置对,你可以先从这两个点入手检查。
内容的提问来源于stack exchange,提问作者shirafuno

