Reactive Web Flux Security 未从 JWT 令牌中提取角色

问题描述 投票:0回答:1

Spring Boot 3.2、Spring Security 6

这是一个 Spring Cloud Gateway,我将其实现为 OAuth2 资源服务器,它也具有执行器端点。我正在尝试使用 JWT 角色来限制对执行器端点的访问。我已经在非反应式堆栈中的另一个微服务中成功完成了此操作。但是,网关不支持 starter-web。我无法找出反应式堆栈中等效的 JWT 转换器。

EnableWebFluxSecurity ServerHttpSecurity 设置为允许基于 jwt 令牌中的 JWT 角色访问执行器端点。但是,在调试时,AbstractAuthenticationToken 仅具有“SCOPE_”授予权限。还应该提取 JWT 用户角色。

pom:


        <!--#### SECURITY ###-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>

        <!--#### REST ###-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

道具:

spring:
  main:
    allow-bean-definition-overriding: true
    web-application-type: reactive
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: "http://localhost:${env.idp-port}/realms/osint-realm"
          jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs

jwt:
  token:
    converter:
      resource-id: osint-keycloak-client
      principal-attribute: preferred_username

服务器过滤器


@Configuration
@EnableWebFluxSecurity
public class WebFluxSecurityConfig {

    @Value("${spring.application.name}")
    private String appName;

    private final CustomJwtTokenConverter customJwtTokenConverter;

    public WebFluxSecurityConfig(TokenConverterProperties tokenConverterProperties) {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        this.customJwtTokenConverter = new CustomJwtTokenConverter(jwtGrantedAuthoritiesConverter, tokenConverterProperties);
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity) {
        serverHttpSecurity
            .csrf(ServerHttpSecurity.CsrfSpec::disable)
            .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
            .authorizeExchange(exchange ->  {
                exchange.pathMatchers("/eureka/**").permitAll();
                exchange.pathMatchers("/" + appName + "/actuator/**").hasRole("actuator");
                exchange.anyExchange().authenticated();
            })
            //This is only pulling scopes not roles?
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults()));

        return serverHttpSecurity.build();
    }

我认为问题出在 JWT Customizer.withDefaults 上。我认为这给了我 SCOPE_profile、SCOPE_email 的匿名权限,这些权限不在提供的 JWT 令牌上

我尝试遵循Spring Security 6.1.4但是,这些片段对于创建自定义 JWT 转换器来说非常模糊。

这是我对客户 JWT 转换器的尝试

public class CustomJwtTokenConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private static final String RESOURCE_ACCESS = "resource_access";
    private static final String ROLES = "roles";
    protected static final String ROLE_PREFIX = "ROLE_";
    private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter;
    private final TokenConverterProperties properties;

    public CustomJwtTokenConverter(
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter,
        TokenConverterProperties properties) {
        this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter;
        this.properties = properties;
    }

    @Override
    public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
        List<Collection<String>> roleResources = Optional.of(jwt)
            .map(token -> token.getClaimAsMap(RESOURCE_ACCESS))
            .map(claimMap -> (Map<String, Object>) claimMap.get(properties.getResourceId()))
            .map(resourceData -> (Collection<String>) resourceData.get(ROLES))
            .stream().collect(Collectors.toList());

        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        roleResources.forEach(role -> role.forEach(roleValue -> simpleGrantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + roleValue))));

        Stream<SimpleGrantedAuthority> accesses = simpleGrantedAuthorities.stream().distinct();

        Set<GrantedAuthority> authorities = Stream
            .concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(), accesses)
            .collect(Collectors.toSet());

        String principalClaimName = properties.getPrincipalAttribute()
            .map(jwt::getClaimAsString)
            .orElse(jwt.getClaimAsString(JwtClaimNames.SUB));

        return new JwtAuthenticationToken(jwt, authorities, principalClaimName);
    }
}

但是,这提供的是 CustomerJwtTokenConverter 而不是过滤器中的转换器:

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity) {
        serverHttpSecurity
            .csrf(ServerHttpSecurity.CsrfSpec::disable)
            .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
            .authorizeExchange(exchange ->  {
                exchange.pathMatchers("/eureka/**").permitAll();
                exchange.pathMatchers("/" + appName + "/actuator/**").hasRole("actuator");
                exchange.anyExchange().authenticated();
            })
            //This is only pulling scopes not roles?
            .oauth2ResourceServer(oauth2 -> oauth2
//                .jwt(Customizer.withDefaults()));
                .jwt(jwtSpec -> jwtSpec.jwtAuthenticationConverter(this.customJwtTokenConverter)));
        return serverHttpSecurity.build();

spring spring-boot spring-security-oauth2
1个回答
0
投票

您的转换器是一个同步转换器(用于 servlet),而不是反应式转换器。

要实现的接口是

Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>>
,而不是
Converter<Jwt, AbstractAuthenticationToken>

请注意,您可能可以使用 Spring security 提供的

ReactiveJwtAuthenticationConverter
实现。这使事情变得更容易,因为当您使用
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
时,Spring Boot 会选择这样的 bean。

@Component
@RequiredArgsConstructor
static class KeycloakClientAuthoritiesConverter implements Converter<Jwt, Flux<GrantedAuthority>> {
    private final TokenConverterProperties properties;

    @Override
    @SuppressWarnings("unchecked")
    public Flux<GrantedAuthority> convert(Jwt source) {
        final var resourceAccess = (Map<String, Object>) source.getClaims().getOrDefault("resource_access", Map.of());
        final var clientAccess = (Map<String, Object>) resourceAccess.getOrDefault(properties.getResourceId(), Map.of());
        final var roles = (List<String>) clientAccess.getOrDefault("roles", List.of());
        return Flux.fromStream(roles.stream()).map(SimpleGrantedAuthority::new).map(GrantedAuthority.class::cast);
    }
}

@Bean
ReactiveJwtAuthenticationConverter authenticationConverter(Converter<Jwt, Flux<GrantedAuthority>> authoritiesConverter) {
    final var authenticationConverter = new ReactiveJwtAuthenticationConverter();
    authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
    authenticationConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
    return authenticationConverter;
}
© www.soinside.com 2019 - 2024. All rights reserved.