本文介绍了如何在 Spring OAuth2 资源服务器中为特定端点实现自定义 Token 授权。通过利用 JWT 的私有声明和 Keycloak 的 mapper 功能,以及自定义的 AbstractAuthenticationToken 实现,可以实现灵活且安全的访问控制策略。文章提供了一种基于订阅数据的访问控制方案,并讨论了如何使用客户端凭据流来认证受信任的客户端。
在标准的 Spring OAuth2 资源服务器配置中,通常使用 JWT 验证来自授权服务器的 Token。然而,在某些情况下,可能需要为特定的端点添加自定义的 Token 验证逻辑。本文将探讨如何实现这种自定义授权,并提供一种基于订阅数据的访问控制方案。
一种有效的方法是将访问控制所需的所有数据存储在 JWT 中。这需要在授权服务器(例如 Keycloak)上配置,以便将相关数据添加到私有声明中。在 Keycloak 中,可以使用 "mappers" 来实现这一点。
以下是一个 Keycloak mapper 的示例,它查询一个 REST API 来获取订阅数据,并在用户登录时将返回的值作为私有声明添加到 access-token 中:
通过将订阅信息添加到 access-token 中,可以在资源服务器上使用这些信息来实现细粒度的访问控制。
actAuthenticationToken为了更方便地访问和使用 JWT 中的私有声明,可以创建一个自定义的 AbstractAuthenticationToken 实现。这个自定义实现可以将私有声明的解析逻辑封装起来,并提供更易于使用的 API。
以下是一个自定义 AbstractAuthenticationToken 实现的示例:
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private final Map attributes;
public CustomAuthenticationToken(Object principal, Map attributes, Collection extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.attributes = attributes;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public Map getAttributes() {
return this.attributes;
}
public T getAttribute(String name, Class type) {
Object value = this.attributes.get(name);
if (value == null) {
return null;
}
return type.cast(value);
}
} 然后,在 jwtAuthenticationConverter bean 中,返回这个自定义的 AbstractAuthenticationToken 实现,而不是默认的 JwtAuthenticationToken。
@Bean public ConverterjwtAuthenticationConverter() { JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter()); return jwt -> { // Extract attributes from JWT claims Map attributes = jwt.getClaims(); // Extract authorities from JWT Collection authorities = jwtConverter.getJwtGrantedAuthoritiesConverter().convert(jwt); // Create custom authentication token return new CustomAuthenticationToken(jwt.getSubject(), attributes, authorities); }; }
有了自定义的 AbstractAuthenticationToken 实现,就可以在 @PreAuthorize 注解中使用更具可读性的安全表达式。
以下是一个使用 @PreAuthorize 注解的示例:
@PreAuthorize("hasAuthority('ROLE_ADMIN') or hasSubscription('premium')")
public String getPremiumContent() {
// ...
}在这个示例中,只有具有 ROLE_ADMIN 权限或具有 premium 订阅的用户才能访问 getPremiumContent() 方法。
如果需要认证不受资源所有者("真实用户")上下文约束的受信任客户端,可以使用客户端凭据流从 Keycloak 获取 access-token。
从资源服务器的角度来看,这将与使用用户 access-token 的请求没有区别:所有请求都将使用由同一授权服务器颁发的 access-token 进行授权。
通过结合 JWT 私有声明、Keycloak mapper 和自定义 AbstractAuthenticationToken 实现,可以实现灵活且安全的自定义 Token 授权方案。这种方法可以根据用户的订阅信息、角色或其他自定义属性来控制对特定端点的访问。此外,使用客户端凭据流可以认证不受资源所有者上下文约束的受信任客户端。
注意事项: