尽管设置了承载令牌,但 Spring Cloud Gateway 重定向到 Keycloak 登录页面
Posted
技术标签:
【中文标题】尽管设置了承载令牌,但 Spring Cloud Gateway 重定向到 Keycloak 登录页面【英文标题】:Spring Cloud Gateway redirects to Keycloak login page although Bearer token is set 【发布时间】:2020-10-13 06:10:58 【问题描述】:我正在使用 Keycloak 作为身份提供者、Spring Cloud Gateway 作为 API 网关和多个微服务的设置。
我可以通过http://localhost:8050/auth/realms/dev/protocol/openid-connect/token
的网关(重定向到 Keycloak)接收 JWT。
我可以使用 JWT 访问直接位于 Keycloak 服务器上的资源(例如 http://localhost:8080/auth/admin/realms/dev/users
)。
但是当我想使用网关将我中继到相同的资源 (http://localhost:8050/auth/admin/realms/dev/users
) 时,我会收到 Keycloak 登录表单作为响应。
我的结论是我的 Spring Cloud Gateway 应用程序中一定存在错误配置。
这是网关中的安全配置:
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository)
// Authenticate through configured OpenID Provider
http.oauth2Login();
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
//Exclude /auth from authentication
http.authorizeExchange().pathMatchers("/auth/realms/ahearo/protocol/openid-connect/token").permitAll();
// Require authentication for all requests
http.authorizeExchange().anyExchange().authenticated();
// Allow showing /home within a frame
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
这是我在网关中的 application.yaml:
spring:
application:
name: gw-service
cloud:
gateway:
default-filters:
- TokenRelay
discovery:
locator:
lower-case-service-id: true
enabled: true
routes:
- id: auth
uri: http://localhost:8080
predicates:
- Path=/auth/**
security:
oauth2:
client:
registration:
keycloak:
client-id: 'api-gw'
client-secret: 'not-relevant-but-correct'
authorizationGrantType: authorization_code
redirect-uri: 'baseUrl/login/oauth2/code/registrationId'
scope: openid,profile,email,resource.read
provider:
keycloak:
issuerUri: http://localhost:8080/auth/realms/dev
user-name-attribute: preferred_username
server:
port: 8050
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka
register-with-eureka: true
fetch-registry: true
如何让网关知道用户已通过身份验证(使用 JWT)而不会将我重定向到登录页面?
【问题讨论】:
能否请您告知您是如何绕过登录页面实现的? 把这个改成:authorization-grant-type: authorization_code 你能绕过登录页面吗?如果是,请与我们分享如何? 【参考方案1】:如果您想使用访问令牌向 Spring Gateway 发出请求,您需要将其设为资源服务器。添加以下内容:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
application.yml
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://.../auth/realms/...
SecurityConfiguration.java
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository)
// Authenticate through configured OpenID Provider
http.oauth2Login();
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
// Require authentication for all requests
http.authorizeExchange().anyExchange().authenticated();
http.oauth2ResourceServer().jwt();
// Allow showing /home within a frame
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
【讨论】:
能否请您在这里指导我 - github.com/SaiUpadhyayula/springboot-microservices-project/…?请查看最后的 cmets【参考方案2】:我绕过了这个问题,直接与 Keycloak 通信,而不通过 Spring Cloud Gateway 将请求转发给它。
这实际上不是一种解决方法,但实际上是最佳实践/据我所知完全可以。
【讨论】:
无需代码,直接寻址您的 Keycloak 服务器即可。所以不要使用api.domain.com(让API将您的请求重定向到Keycloak)使用keycloak.domain.com(直接将请求发送到Keycloak实例)。【参考方案3】:此代码用于Client_credentials
grant_type。如果您使用其他授权类型,则需要在请求参数中添加client_id
和client_secret
。
public class MyFilter2 extends OncePerRequestFilter
private final ObjectMapper mapper = new ObjectMapper();
@Value("$auth.server.uri")
private String authServerUri;
@Value("$client_id")
private String clientId;
@Value("$client_secret")
private String clientSecret;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws IOException
try
String token = httpServletRequest.getHeader("Authorization");
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type","application/x-www-form-urlencoded");
headers.set("Authorization",token);
final HttpEntity finalRequest = new HttpEntity("", headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(authServerUri,finalRequest,String.class);
if (!HttpStatus.OK.equals(response.getStatusCode()))
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("status", HttpStatus.UNAUTHORIZED.value());
errorDetails.put("message", "Invalid or empty token");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
else
filterChain.doFilter(httpServletRequest, httpServletResponse);
catch(HttpClientErrorException he)
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("status", HttpStatus.UNAUTHORIZED.value());
errorDetails.put("message", "Invalid or empty token");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
catch (Exception exception)
【讨论】:
以上是关于尽管设置了承载令牌,但 Spring Cloud Gateway 重定向到 Keycloak 登录页面的主要内容,如果未能解决你的问题,请参考以下文章
忽略 Spring Boot 中特定 Url 的承载令牌验证