我正在使用Spring Boot设置资源服务器,并使用Spring Security提供的OAuth2保护端点。所以我正在使用Spring Boot 2.1.8.RELEASE,例如,它使用Spring Security 5.1.6.RELEASE。
2.1.8.RELEASE
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,该解决方案可以将特定于资源的角色添加到Authority集合中。
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它实际上已被弃用,因此应采用成熟的转换器来实现更面向未来的解决方案
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授予的适当位置。