У меня есть сервер Spring Authorization / Resource, управляющий авторизацией клиента Webflux Oauth2. Сам по себе это работает хорошо. Когда я добавляю Spring Cloud Gateway и получаю доступ к Webflux через шлюз, я вижу, что сервер авторизации проверяет пользователя, а Webflux возвращает 302, но на шлюзе я получаю 400 «Неверный идентификатор регистрации клиента».
Авторизация/ Сервер ресурсов:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Starter;
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Свойства;
server:
port: 8889
Конфигурация сервера;
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("myClientId2")
.secret(passwordEncoder.encode("mySecret2"))
.authorizedGrantTypes("authorization_code")
.scopes("all")
.autoApprove(true)
.redirectUris("http://localhost:8083/login/oauth2/code/myAppTwo")
.and()
.withClient("myGatewayId")
.secret(passwordEncoder.encode("myGatewaySecret"))
.authorizedGrantTypes("authorization_code")
.scopes("all")
.autoApprove(true)
.redirectUris("http://localhost:8888/login/oauth2/code/gateway")
;
}
}
Безопасность;
@Configuration
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and().authorizeRequests().anyRequest().authenticated()
.and().formLogin().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("x").password(passwordEncoder().encode("123"))
.roles("USER");
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Ответ пользователя;
@RestController
public class UserController {
@GetMapping("/user")
public Principal user(Principal principal) {
System.err.println(principal);
return principal;
}
Свойства Webflux:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
</dependencies>
;
server:
port: 8083
spring:
security:
oauth2:
client:
registration:
myAppTwo:
client-name: Test8082
client-id: myClientId2
client-secret: mySecret2
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
myAppTwo:
authorization-uri: http://localhost:8889/oauth/authorize
token-uri: http://localhost:8889/oauth/token
user-info-uri: http://localhost:8889/user
user-name-attribute: name
Конечная точка Webflux;
@RestController
public class Controller {
@GetMapping(value = "/test3")
Mono<OAuth2User> getTest3(@AuthenticationPrincipal Mono<OAuth2User> ouser) {
System.err.println("o =" + ouser);
return ouser;
}
}
Запуск http://localhost:8083/test3
через Chrome перенесет меня на страницу входа на сервере авторизации, после чего я введу учетные данные, а затем приведенный выше URL вернет ожидаемый ответ.
Когда я добавляю шлюз;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
со свойствами;
server:
port: 8888
spring:
autoconfigure:
# TODO: remove when fixed https://github.com/spring-projects/spring-security/issues/17949
# Without this line, Gateway tries to log in using it's own security, rather than deferring to OAuth2
exclude: org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration
cloud:
gateway:
routes:
- id: oauth
predicates:
- Path=/oauth2/**
uri: http://localhost:8889
filters:
- TokenRelay=
- RemoveRequestHeader=Cookie
- id: route2
predicates:
- Path=/2/**
uri: http://localhost:8083
filters:
- RewritePath=/2/(?<myPath>.*), /$\{myPath}
- TokenRelay=
- RemoveRequestHeader=Cookie
security:
oauth2:
client:
registration:
gateway:
client-id: myGatewayId
client-secret: myGatewaySecret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider: gateway
provider:
gateway:
authorization-uri: http://localhost:8889/oauth/authorize
token-uri: http://localhost:8889/oauth/token
user-info-uri: http://localhost:8889/user
user-name-attribute: name
jwk-set-uri: http://localhost:8889/oauth/token_key
Теперь, если я попытаюсь http://localhost:8888/2/test3
, я все еще буду перенаправлен на вход в систему. toString
на сервере авторизации регистрирует учетные данные, Webflux по-прежнему выдает 302, но шлюз делает следующее:
Route matched: route2
Mapping [Exchange: GET http://localhost:8888/2/test3] to Route{id='route2', uri=http://localhost:8083, order=0, predicate=Paths: [/2/**], match trailing slash: true, gatewayFilters=[[[RewritePath /2/(?<myPath>.*) = '/${myPath}'], order = 1], [org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory$$Lambda$666/1724736027@4b96b22, order = 2], [[RemoveRequestHeader name = 'Cookie'], order = 3]]}
[98bb6dad] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@6fa90b79
Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@1894593a}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@b835727}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@7fd26ad8}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@7cea0110}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@5527b211}, order = 0], [[RewritePath /2/(?<myPath>.*) = '/${myPath}'], order = 1], [org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory$$Lambda$666/1724736027@4b96b22, order = 2], [[RemoveRequestHeader name = 'Cookie'], order = 3], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@14b0e127}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration$NoLoadBalancerClientFilter@54cf7c6a}, order = 10100], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@468dda3e}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@3d37203b}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@10823d72}, order = 2147483647]]
"GET /2/test3 HTTP/1.1" 302 0 8888 8 ms
Resolved [ResponseStatusException: 400 BAD_REQUEST "Invalid client registration id"] for HTTP GET /oauth2/authorization/myAppTwo
Writing "<html><body><h1>Whitelabel Error Page</h1><p>This application has no configured error view, so you (truncated)...
"GET /oauth2/authorization/myAppTwo HTTP/1.1" 400 312 8888 2 ms
"GET /favicon.ico HTTP/1.1" 302 0 8888 2 ms
"GET /oauth2/authorization/gateway HTTP/1.1" 302 0 8888 2 ms
Writing form fields [grant_type, code, redirect_uri] (content masked)
Decoded [{access_token=2c14f1e7-5d95-4fed-be95-2d8518d1fb48, token_type=bearer, expires_in=40912, scope=all}]
Decoded [{authorities=[{authority=ROLE_USER}], details={remoteAddress=127.0.0.1, sessionId=null, tokenValue=2 (truncated)...]
"GET /login/oauth2/code/gateway?code=fGdiyz&state=ZynriL0UtU37b4rMfDHM7JheNYYo0UnZQvgu4U2kWwQ%3D HTTP/1.1" 302 0 8888 82 ms
"GET / HTTP/1.1" 200 3348 8888 5 ms
Через ту же сессию браузера , если я пытаюсьпрямой URL http://localhost:8083/test
, он отвечает правильно (т. е. я вошел в систему, и 8083 имеет мои учетные данные).
Я вижу с помощью отладки, что DefaultServerOAuth2AuthorizationRequestResolver.findByRegistrationId
сначала проверяет перенаправление на gateway
и проходит. Тем не менее, он пытается и терпит неудачу на втором проходе для перенаправления myAppTwo
, который не был определен YML шлюза, и я предполагаю, что не должно быть. Удаление маршрута oauth
, похоже, никак не повлияет. Я никогда не вижу, чтобы этот маршрут попадал.
Так как мне заставить шлюз перенаправить на myAppTwo
на порту 8083?
Обновление Когда я использую KeyCloak вместомой собственный AuthorizationServer / ResourceServer Я получаю ту же проблему.