基于角色的自定义OAuth2授权类型实现
在Spring Security OAuth2中,标准的授权模式包括授权码、隐式、密码、客户端凭证四种。但在实际业务场景中,可能需要引入额外的身份验证维度,例如结合用户角色进行令牌发放。本文将演示如何扩展OAuth2的TokenGranter机制,实现一个基于用户名、密码和指定角色的自定义授权方式。
核心在于实现一个新的TokenGranter子类,用于处理特定的grant_type请求。我们定义一种名为password_with_role的授权类型,要求客户端提供username、password以及期望激活的role参数。
1. 自定义TokenGranter实现
public class RoleBasedTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "password_with_role";
private final CustomUserDetailsService userDetailsService;
public RoleBasedTokenGranter(AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetails,
OAuth2RequestFactory requestFactory,
CustomUserDetailsService userDetailsService) {
super(tokenServices, clientDetails, requestFactory, GRANT_TYPE);
this.userDetailsService = userDetailsService;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new HashMap<>(tokenRequest.getRequestParameters());
String username = parameters.get("username");
String password = parameters.get("password");
String targetRole = parameters.get("role");
if (username == null || password == null || targetRole == null) {
throw new InvalidRequestException("Missing required parameters: username, password or role");
}
// 加载带有角色过滤的用户身份信息
UserDetails userDetails = userDetailsService.loadUserByUsernameAndPasswordAndRole(username, password, targetRole);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
OAuth2Request storedRequest = tokenRequest.createOAuth2Request(client);
return new OAuth2Authentication(storedRequest, authentication);
}
}
2. 用户身份与权限加载服务
创建专门的服务类来根据账户、密码及角色查询并组装安全上下文所需的UserDetails对象。
@Service
public class CustomUserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
public UserDetails loadUserByUsernameAndPasswordAndRole(String username, String rawPassword, String roleName) {
UserEntity user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
if (!BCrypt.checkpw(rawPassword, user.getPasswordHash())) {
throw new BadCredentialsException("Invalid password");
}
RoleEntity role = roleRepository.findByNameAndUserId(roleName, user.getId())
.orElseThrow(() -> new AccessDeniedException("Role not assigned to user"));
Collection<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName().toUpperCase()));
// 添加该角色关联的权限(如菜单权限)
List<PermissionEntity> permissions = roleRepository.findPermissionsByRoleId(role.getId());
permissions.forEach(p -> {
if (p.getPermCode() != null && !p.getPermCode().isEmpty()) {
authorities.add(new SimpleGrantedAuthority(p.getPermCode()));
}
});
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPasswordHash())
.authorities(authorities)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
}
}
3. 集成到授权服务器配置
将自定义的TokenGranter注入到授权端点,并组合原有授权器形成复合授权链。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationServerTokenServices tokenServices;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private OAuth2RequestFactory requestFactory;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.tokenServices(tokenServices)
.requestFactory(requestFactory);
// 使用增强的TokenGranter链
endpoints.tokenGranter(compositeTokenGranter(endpoints));
}
private TokenGranter compositeTokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
granters.add(new RoleBasedTokenGranter(
endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(),
customUserDetailsService));
return new CompositeTokenGranter(granters);
}
}
通过上述实现,客户端可发送如下请求以获取访问令牌:
POST /oauth/token
grant_type=password_with_role&username=admin&password=123456&role=ADMIN
系统将校验用户凭据的同时检查其是否拥有指定角色,并基于此生成包含相应权限的JWT或持久化令牌。