spring - Spring Security:将 OAuth2 声明与角色映射到保护资源服务器端点
问题描述
我正在使用 Spring Boot 设置资源服务器并保护我使用 Spring Security 提供的 OAuth2 的端点。所以我使用的是 Spring Boot 2.1.8.RELEASE
,例如使用 Spring Security 5.1.6.RELEASE
。
作为授权服务器,我正在使用 Keycloak。资源服务器中身份验证、颁发访问令牌和验证令牌之间的所有过程都正常工作。下面是一个发行和解码的令牌的例子(有些部分被剪掉了):
{
"jti": "5df54cac-8b06-4d36-b642-186bbd647fbf",
"exp": 1570048999,
"aud": [
"myservice",
"account"
],
"azp": "myservice",
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"myservice": {
"roles": [
"ROLE_user",
"ROLE_admin"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email offline_access microprofile-jwt profile address phone",
}
如何配置 Spring Security 以使用访问令牌中的信息为不同的端点提供条件授权?
最终我想写一个这样的控制器:
@RestController
public class Controller {
@Secured("ROLE_user")
@GetMapping("userinfo")
public String userinfo() {
return "not too sensitive action";
}
@Secured("ROLE_admin")
@GetMapping("administration")
public String administration() {
return "TOOOO sensitive action";
}
}
解决方案
在搞砸了一点之后,我找到了一个实现 custom 的解决方案jwtAuthenticationConverter
,它能够将特定于资源的角色附加到权限集合中。
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new JwtAuthenticationConverter()
{
@Override
protected Collection<GrantedAuthority> extractAuthorities(final Jwt jwt)
{
Collection<GrantedAuthority> authorities = super.extractAuthorities(jwt);
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource = null;
Collection<String> resourceRoles = null;
if (resourceAccess != null &&
(resource = (Map<String, Object>) resourceAccess.get("my-resource-id")) !=
null && (resourceRoles = (Collection<String>) resource.get("roles")) != null)
authorities.addAll(resourceRoles.stream()
.map(x -> new SimpleGrantedAuthority("ROLE_" + x))
.collect(Collectors.toSet()));
return authorities;
}
});
其中my-resource-id既是显示在resource_access声明中的资源标识符,也是与ResourceServerSecurityConfigurer中的 API 关联的值。
请注意,这extractAuthorities
实际上已被弃用,因此更面向未来的解决方案应该是实施一个成熟的转换器
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
{
private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt, final String resourceId)
{
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource;
Collection<String> resourceRoles;
if (resourceAccess != null && (resource = (Map<String, Object>) resourceAccess.get(resourceId)) != null &&
(resourceRoles = (Collection<String>) resource.get("roles")) != null)
return resourceRoles.stream()
.map(x -> new SimpleGrantedAuthority("ROLE_" + x))
.collect(Collectors.toSet());
return Collections.emptySet();
}
private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
private final String resourceId;
public CustomJwtAuthenticationConverter(String resourceId)
{
this.resourceId = resourceId;
}
@Override
public AbstractAuthenticationToken convert(final Jwt source)
{
Collection<GrantedAuthority> authorities = Stream.concat(defaultGrantedAuthoritiesConverter.convert(source)
.stream(),
extractResourceRoles(source, resourceId).stream())
.collect(Collectors.toSet());
return new JwtAuthenticationToken(source, authorities);
}
}
我已经使用 Spring Boot 2.1.9.RELEASE、Spring Security 5.2.0.RELEASE 和官方 Keycloak 7.0.0 Docker 映像测试了这两种解决方案。
一般来说,我认为无论实际的授权服务器(即 IdentityServer4、Keycloak ...)如何,这似乎都是将声明转换为 Spring Security 授权的合适位置。
推荐阅读
- java - 排序的文本文件数组缺少单词(Java)
- c++ - 我得到一个表达错误必须是可修改的
- python - 为什么不在布尔字典中理解为函数调用?
- r - 使用 group_by, R 识别价值随时间的变化
- sql - 有两个条件的 jaro-winkler
- arrays - 如何使用 C 找到子数组中所有元素的总和为 0 的最大子数组的长度我不知道错误在哪里.. 请任何人告诉我
- excel - Excel VBA 3704 错误(无法打开与 AdoDB 的连接)
- python-3.x - 按单元格值提取 excel 数据:python PANDAS
- html - 使用 Typescript/Angular 通过 RXJS 隐藏 HTML 元素
- r - PSM:从 r 中的 MachIT 包中提取组