Unable to Trigger UserDetailsService' loadUserByUsername Method in Spring Security OAuth2 Password Mode

34 views Asked by At

I encountered an issue while configuring an OAuth2 authorization server using Spring Security. I've set up AuthorizationServerConfig with a custom UserDetailsService (userServiceDetailsServiceImpl). However, when performing OAuth2 authentication in password mode, I'm unable to trigger the loadUserByUsername method.

I attempted to access the /oauth/token endpoint using Basic Authentication, ensuring that the provided username and password are correct. Despite configuring UserDetailsService, I couldn't reach the loadUserByUsername method within the UserServiceDetailsServiceImpl.

postman

Related Code Snippet:

@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;
    }
}
@EnableResourceServer
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {}
@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();
    }
}
@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());
    }
}
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

Expected Outcome: I expect to enter the loadUserByUsername method within the UserServiceDetailsServiceImpl during OAuth2 password mode authentication to validate the user's identity.

Attempted Solutions:

Properly injected UserDetailsService in AuthorizationServerConfig and configured it in AuthorizationServerEndpointsConfigurer. Checked Basic Authentication configurations and headers to ensure correct username and password input. Verified logging; there are no records indicating the invocation of the loadUserByUsername method.

Requesting Help:

Has anyone encountered a similar issue? How was it resolved? What aspects should I check to determine why the loadUserByUsername method isn't triggered? Are there other factors that might affect OAuth2 password mode authentication? Thank you very much for your assistance and suggestions!

0

There are 0 answers