You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security OAuth2密码模式下无法触发loadUserByUsername方法

OAuth2密码模式下自定义UserDetailsService的loadUserByUsername方法未触发问题

我在配置Spring Security OAuth2授权服务器时遇到问题:已经在AuthorizationServerConfig中配置了自定义UserDetailsService(userServiceDetailsServiceImpl),但使用OAuth2密码模式认证时,始终无法触发loadUserByUsername方法。

我通过Basic Authentication访问/oauth/token端点,确认用户名和密码正确,但就是进不了UserServiceDetailsServiceImpl的loadUserByUsername方法。

Postman请求截图

相关代码片段

AuthorizationServerConfig

@EnableAuthorizationServer // 开启授权服务器
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Qualifier("userServiceDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 配置验证管理器,UserdetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenStore(jwtTokenStore()) // jwt 来存储我们的token
                .tokenEnhancer(jwtAccessTokenConverter());  // jwt还需要设置决定token的产生方式
        super.configure(endpoints);
    }

    private TokenStore jwtTokenStore() {
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
        return jwtTokenStore;
    }

    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        // 加载我们的私钥
        ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
        jwtAccessTokenConverter.setKeyPair(
                keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
        return jwtAccessTokenConverter;
    }
}

ResourceServerConfig

@EnableResourceServer
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {}

WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().anyRequest().authenticated();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 密码加密
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

UserServiceDetailsServiceImpl

@Service
public class UserServiceDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String loginType = requestAttributes.getRequest().getParameter("login_type");   // 区分后台人员还是用户
        if(StringUtils.isEmpty(loginType)) {
            throw new AuthenticationServiceException("登陆类型不能为null");
        }
        UserDetails userDetails = null;
        try {
            switch (loginType) {
                case LoginConstant.ADMIN_TYPE:
                    userDetails =  loadSysUserByUsername(s); // 后台人员的登录
                    break;
                case LoginConstant.MEMBER_TYPE:
                    userDetails = loadMemberUserByUsername(s);  // 普通会员登录
                    break;
                default:
                    throw new AuthenticationServiceException("暂不支持的登陆方式:" + loginType);
            }
        }catch (IncorrectResultSizeDataAccessException e) { // 用户不存在
            throw new UsernameNotFoundException("用户名" + s + "不存在");
        }
        return userDetails;
    }
    /**
     * 后台管理员登录
     * @param username
     * @return
     */
    private UserDetails loadSysUserByUsername(String username) {
        // 1. 使用用户名查询用户
        return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_SQL, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int rowNum) throws SQLException {
                if(resultSet.wasNull()) {
                    throw new UsernameNotFoundException("用户名" + username + "不存在");
                }
                long id = resultSet.getLong("id");  // 用户id
                String password = resultSet.getString("password");  // 用户密码
                int status = resultSet.getInt("status");    // 用户状态
                return new User(   // 3. 封装成一个USerDetails对象,返回
                        String.valueOf(id), // 使用id->username
                        password,
                        status == 1,
                        true,
                        true,
                        true,
                        getSysUserPermissions(id)           // 2. 查询该用户的权限
                );
            }
        }, username);
    }

    /**
     * 通过用户id查询用户的权限
     * @param id
     * @return
     */
    private Collection<? extends GrantedAuthority> getSysUserPermissions(long id) {
        // 1. 当用户为超级管理员,他拥有所有的权限
        String roleCode = jdbcTemplate.queryForObject(LoginConstant.QUERY_ROLE_CODE_SQL, String.class);
        List<String> permissions = null;    // 权限的名称
        if(LoginConstant.ADMIN_ROLE_CODE.equals(roleCode)) {    // 超级管理员
            permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_ALL_PERMISSIONS, String.class);
        }
        else {          // 2. 普通用户,需要使用角色->权限
            permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_PERMISSION_SQL, String.class, id);
        }
        if(permissions == null || permissions.isEmpty()) {
            return Collections.emptyList();
        }
        return permissions.stream()
                .distinct()     // 去重
                .map(perm->new SimpleGrantedAuthority(perm))
                .collect(Collectors.toSet());
    }
}

application.yml

server:
  port: 9999
spring:
  application:
    name: authorization-server
  cloud:
    nacos:
      discovery:
        server-addr: nacos-server:8848
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/coin-exchange?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver

预期效果

希望在OAuth2密码模式认证过程中,能够进入UserServiceDetailsServiceImpl的loadUserByUsername方法,完成用户身份验证。

已尝试的解决方案

  • 在AuthorizationServerConfig中正确注入UserDetailsService,并在AuthorizationServerEndpointsConfigurer中完成配置。
  • 检查Basic Authentication的配置和请求头,确保用户名和密码输入正确。
  • 查看日志,未发现loadUserByUsername方法被调用的记录。

求助问题

  • 是否有人遇到过类似问题?是如何解决的?
  • 我应该检查哪些方面来确定loadUserByUsername方法未被触发的原因?
  • 还有哪些因素可能会影响OAuth2密码模式的认证流程?

非常感谢您的帮助和建议!

内容的提问来源于stack exchange,提问作者Caseythekiwi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:42:05