6

我正在使用 Keycloak 作为身份提供者、Spring Cloud Gateway 作为 API 网关和多个微服务的设置。我可以通过我的网关(重定向到 Keycloak)通过http://localhost:8050/auth/realms/dev/protocol/openid-connect/token.

我可以使用 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)而不会将我重定向到登录页面?

4

3 回答 3

3

如果要使用访问令牌向 Spring Gateway 发出请求,则需要将其设为资源服务器。添加以下内容:

pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>

应用程序.yml

  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://.../auth/realms/...

安全配置.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();
}
于 2021-04-02T17:53:52.510 回答
0

此代码用于Client_credentialsgrant_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) {
    }
}
于 2021-09-02T08:56:26.060 回答
0

我通过直接与 Keycloak 通信绕过了这个问题,而没有通过 Spring Cloud Gateway 将请求转发给它。

据我所知,这实际上不是一种解决方法,但实际上是最佳实践/完全可以。

于 2021-02-24T21:16:38.063 回答