我有一个spring云网关服务作为Oauth2客户端和令牌中继,它是用Oauth2资源服务器配置的。使用Okta作为授权服务器,这种设置可以很好地工作,但更改为谷歌,当网关尝试请求安全服务时,会产生HTTP 401。以下是我的配置:
网关应用程序yml
server:
port: 80
spring:
application:
name: gateway-service
security:
oauth2:
client:
registration:
okta:
provider: okta
client-id: ${OKTA_OAUTH2_CLIENT_ID}
client-secret: ${OKTA_OAUTH2_CLIENT_SECRET}
authorization-grant-type: authorization_code
redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid,profile,email
google:
provider: google
client-id: ${GOOGLE_OAUTH2_CLIENT_ID}
client-secret: ${GOOGLE_OAUTH2_CLIENT_SECRET}
authorization-grant-type: authorization_code
redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid,profile,email
provider:
okta:
issuer-uri: <my_okta_uri>
google:
issuer-uri: https://accounts.google.com
cloud:
gateway:
routes:
- id: user
uri: lb://USER-SERVICE
predicates:
- Path=/user/**
filters:
- TokenRelay=
- RewritePath=/user(?<params>/?.*), ${params}
- id: category
uri: lb://CATEGORY-SERVICE
predicates:
- Path=/category/**
filters:
- RewritePath=/category(?<params>/?.*), ${params}
- id: income
uri: lb://INCOME-SERVICE
predicates:
- Path=/income/**
filters:
- RewritePath=/income(?<params>/?.*), ${params}
- id: expense
uri: lb://EXPENSE-SERVICE
predicates:
- Path=/expense/**
filters:
- RewritePath=/expense(?<params>/?.*), ${params}
- id: budget
uri: lb://BUDGET-SERVICE
predicates:
- Path=/budget/**
filters:
- RewritePath=/budget(?<params>/?.*), ${params}
management:
endpoints:
web:
exposure:
include: "*"
logging:
level:
root: INFO
org:
springframework:
cloud:
gateway:
filter: TRACE
受保护的服务应用程序.yml
server:
port: 8081
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
application:
name: user-service
data:
mongodb:
uri: ${MONGO_URI}
security:
oauth2:
resourceserver:
jwt:
# issuer-uri: <my_okta_uri>
issuer-uri: https://accounts.google.com
management:
endpoints:
web:
exposure:
include: "*"
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.web.HttpLogging: DEBUG
org.springframework.security: DEBUG
org.springframework.security.oauth2: DEBUG
org.springframework.cloud.gateway: DEBUG
我在Auth0中遇到了类似的问题。看起来TokenRelay过滤器没有将JWT作为Bearer令牌进行传递。它提供的访问令牌与OAuth2AuthorizedClient不同。可能有一种方法可以提供一个不同的OAuth2AuthorizedClient或进行不同的配置,但我无法解决。
然而,提供给过滤器的交换已经通过了身份验证,所以它有我想发送给后端服务的JWT令牌。我想这是因为我的后端服务端点被配置为经过身份验证:
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.pathMatchers("/service/**").authenticated()
.pathMatchers("/**").permitAll();
http.oauth2Login();
return http.build();
}
所以我写了一个自定义的OidcTokenRelayGatewayFilterFactory:
public class OidcTokenRelayGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
public OidcTokenRelayGatewayFilterFactory() {
super(Object.class);
}
@Override
public String name() {
return "OidcTokenRelay";
}
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> exchange.getPrincipal()
.filter(principal -> principal instanceof OAuth2AuthenticationToken)
.cast(OAuth2AuthenticationToken.class)
.map(OAuth2AuthenticationToken::getPrincipal)
.filter(principal -> principal instanceof OidcUser)
.cast(OidcUser.class)
.map(token -> withOidcIdToken(exchange, token.getIdToken()))
.defaultIfEmpty(exchange).flatMap(chain::filter);
}
private ServerWebExchange withOidcIdToken(ServerWebExchange exchange, OidcIdToken accessToken) {
return exchange.mutate().request(r -> r.headers(headers -> headers.setBearerAuth(accessToken.getTokenValue())))
.build();
}
}
然后我为FilterFactory注册了一个bean,就像最初的TokenRelayGatewayFilterFactory一样:
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@ConditionalOnClass({ OAuth2AuthorizedClient.class, SecurityWebFilterChain.class, SecurityProperties.class })
@ConditionalOnEnabledFilter(OidcTokenRelayGatewayFilterFactory.class)
public class OidcTokenRelayConfiguration {
@Bean
public OidcTokenRelayGatewayFilterFactory myTokenRelayGatewayFilterFactory() {
return new OidcTokenRelayGatewayFilterFactory();
}
}
最后,我更新了我的Spring配置文件,以使用我的自定义过滤器。
spring:
application:
name: cluster-api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
filters:
- OidcTokenRelay=
- StripPrefix=2
predicates:
- Path="/service/{serviceId}/**"
通过这些更改,我可以将请求路由到后端服务,而不会收到401响应。