密码授权模式下Spring Security OAuth2授权服务器在用户密码错误时重复调用UserDetailsService的问题分析
我之前也碰到过这个一模一样的问题:当使用密码授权模式(password grant type)请求Access Token时,如果用户输入错误密码,授权服务器会反复调用自定义的UserDetailsService,还会多次发起数据库查询,而不是直接返回“密码错误”的提示。从你提供的日志里也能明显看到,多次出现认证失败:密码与存储值不匹配以及重复的数据库连接请求,这就是重复调用loadUserByUsername方法导致的。
问题根源
你已经精准排查到了关键:就是SecurityConfig里被注释掉的那行.and().parentAuthenticationManager(authenticationManagerBean())。当在AuthenticationManagerBuilder中配置父AuthenticationManager时,会触发循环认证流程:OAuth2的TokenEndpoint处理密码模式请求时会调用认证管理器,而如果配置了父管理器,这个认证操作会被重复执行,导致UserDetailsService的loadUserByUsername方法被多次调用——每次调用都会去数据库查询用户信息、角色和权限,造成不必要的资源消耗。
解决方案
保持这行代码的注释状态,不要启用父AuthenticationManager的配置。正确的configure(AuthenticationManagerBuilder auth)配置只需要指定自定义的UserDetailsService和密码编码器即可:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService) .passwordEncoder(passwordEncoder); // 务必保持这行注释,避免循环认证导致重复调用UserDetailsService // .and().parentAuthenticationManager(authenticationManagerBean()) }
为什么会重复调用?
当输入错误密码时,第一次认证失败后,如果配置了父AuthenticationManager,认证流程会被再次触发,导致UserDetailsService被重复调用。从你的日志里可以看到,多次出现ProviderManager尝试认证的记录,以及多次从数据源获取JDBC连接——这就是每次调用loadUserByUsername时执行的数据库查询操作。
调整后,当用户输入错误密码时,认证流程只会执行一次,UserDetailsService被调用一次,然后直接返回密码错误的提示,不会再重复触发认证操作。
内容的提问来源于stack exchange,提问作者user3940641

