Spring Cloud Gateway增强了来自外部OAuth授权服务器的JWT令牌

时间:2019-10-26 18:20:43

标签: spring cloud spring-webflux api-gateway

我有以下Securing Services with Spring Cloud Gateway在Keycloak上工作。对此进行扩展,我将路由到许多Webflux资源服务器。

但是,外部授权服务器提供的范围非常简单。因此,我需要从JWT中获取微服务ID和用户ID,然后检查一个数据库,然后将其转换为他们的团队,该团队将提供可访问的静态页面和URL REST端点的列表。所以我生成的代码想要类似的东西;

...
  .matcher("/page1.html").hasAuthority("SERVICE1_PAGE1")
  .matcher("/page2.html").hasAuthority("SERVICE1_PAGE2")


@PreXXX("hasAuthority('SERVICE1_getAll'))
public List<String> getAll() {...}

我假设可以通过Extracting Authorities Manually在微服务级别上做到这一点,但是通过这样做,我需要在每个微服务中复制此代码。

编辑: 这就是我当前正在做的事情,方法是使用WebClient调用另一个返回已更正权限的通用微服务。但是,如果授权微服务的URL是网关地址,则网关绝不会尝试解析URL。如果使用它的显式URL,即使使用ServerBearerExchangeFilterFunction,我也会从授权微服务获得401。如果我将permitAll()放在返回授权的微服务上,它将起作用。

我可以站立自己的授权服务器并使用TokenEnhancer。但是,通过这样做,我假设我需要为每个可能的微服务加载用户的所有可能的权限(因为我目前不知道用户的去向),并且可能会导致大量数据

理想情况下,我想将此集中在网关中,并让TokenRelay过滤器识别路由并以某种方式增强令牌。这可能吗?

有人可以推荐最好的方法吗?

1 个答案:

答案 0 :(得分:0)

这是我最终使用Spring 2.2.2 Cloud Gateway,Eureka和KeyCloak实施的方法。

安全微服务。

@GetMapping(value = "/grantedAuthorities/{applicationName}/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    // JWT not sent when called within jwtAuthenticationConverter, so pass user id as param !!
public Flux<String> getUsersApplicationAuthorities(@PathVariable String applicationName, @PathVariable String userId) {
    return Flux.fromIterable(roleRepository.getRolesByUserId(applicationName, userId));
}

这将返回类似的内容

SCREEN1_READ
SCREEN2_WRITE

我的其他微服务。

public class SecurityConfig {
    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges ->
                exchanges                   
                    .pathMatchers("/screen1.html").hasAnyAuthority("SCREEN1_READ", "SCREEN1_WRITE")
                    .pathMatchers("/screen2.html").hasAnyAuthority("SCREEN1_READ", "SCREEN2_WRITE")
                    .anyExchange().authenticated()
            )
            .oauth2ResourceServer(spec ->
                spec.jwt().jwtAuthenticationConverter(jwt -> {

                /* ServerBearerExchangeFilterFunction does not work here! So I have to send userId instead of JWT */

                    WebClient webClient = loadBalancedWebClientBuilder().build();
                    String userId =  jwt.getClaimAsString("preferred_username").toUpperCase();
                    String uri = "lb://SECURITY/drs/grantedAuthorities/" + applicationName + "/" + userId;
                    return webClient.get().uri(uri)
                            .retrieve()
                            .bodyToFlux(String.class)
                            .map(s -> new SimpleGrantedAuthority(s))
                            .doOnNext(l -> log.info("Has authority " + l))
                            .collectList()
                            .map(gaList -> new JwtAuthenticationToken(jwt, gaList));
                })
            )
            .csrf().disable()
            .cors().disable();

        return http.build();
    }
}

用于在读/写Thymeleaf时启用/禁用按钮;

@Controller
public class PageController {
    @GetMapping("/screen1.html")
    public String index(@AuthenticationPrincipal JwtAuthenticationToken jwt, Model model) {
        model.addAttribute("update", jwt.getAuthorities().contains(new SimpleGrantedAuthority("SCREEN1_WRITE"))); 
        return "screen1";
    }
}
<html>
    <body>
        <div id="updateableScreen" th:attr="data-update=${update}"></div>   
    </body>
</html>

之后,使用JQuery / Javascript通过选中$("#updateableScreen")

来更改对表单控件的访问