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.
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!