用于从外部OAuth授权服务器增强JWT令牌的Spring Cloud Gateway

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

我有以下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在微服务级别上做到这一点,但是通过这样做,我将需要在每个微服务中复制此代码。

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

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

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

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

spring cloud spring-webflux api-gateway
1个回答
0
投票
安全微服务。

@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")来更改对表单控件的访问权限
© www.soinside.com 2019 - 2024. All rights reserved.