Spring 安全 : mapping OAuth2 claims with roles to secure Resource Server endpoints

标签 spring rest spring-boot spring-security authorization

我正在使用 Spring Boot 设置资源服务器并保护我使用 Spring Security 提供的 OAuth2 的端点。所以我使用的是 Spring Boot 2.1.8.RELEASE例如使用 Spring Security 5.1.6.RELEASE .

作为授权服务器,我使用 Keycloak。资源服务器中身份验证、颁发访问 token 和验证 token 之间的所有过程都正常工作。下面是一个发行和解码 token 的例子(有一些部分被剪掉了):

{
  "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 以使用访问 token 中的信息为不同端点提供条件授权?

最终我想写一个这样的 Controller :

@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";
    }
}

最佳答案

再多折腾之后,我找到了一个实现自定义 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实际上已弃用,因此更面向 future 的解决方案应该是实现成熟的转换器

    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 授权的正确位置。

关于 Spring 安全 : mapping OAuth2 claims with roles to secure Resource Server endpoints,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58205510/

相关文章:

java - 默认 log4j 策略

java - org.springframework.web.servlet.DispatcherServlet noHandlerFound(警告: No mapping found for HTTP request)

java - 如果文件不存在则返回空响应

node.js - 在 Travis CI 上测试使用 Node.js(Express) 构建的 REST api

authentication - 以 RESTful 方式许可和 session

java - 当参数值为空时如何在@Cacheable中创建多个缓存键

java - Spring 启动: 400 Bad request error even when parameter is present

java - Spring Integration JDBC 入站轮询器基于 Java 的配置

java - JSP访问HashMultimap异常是什么原因

java - 剩余图像字节数