我有以下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
过滤器识别路由并以某种方式增强令牌。这可能吗?
有人可以推荐最好的方法吗?
答案 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")