Keycloak + Spring Cloud Gateway + Angular 9

Posted

技术标签:

【中文标题】Keycloak + Spring Cloud Gateway + Angular 9【英文标题】: 【发布时间】:2020-09-11 09:06:58 【问题描述】:

我尝试使用 Keycloak 作为 IdP 保护我的 Spring Cloud Gateway,但是当我通过网关发送请求时,响应总是将我重定向到登录页面。

Keycloak 领域有三个客户端:

团队门户(前端 [公共]) 网关(后端 [机密]) 培训服务(后端 [仅裸机])

Angular 通过浏览器生成的请求是这样的:

OPTIONS /trainings/exercises HTTP/1.1
Host: localhost:8762
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: http://localhost:4201
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Sec-Fetch-Dest: empty
Referer: http://localhost:4201/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37
Accept-Encoding: gzip, deflate, br
Accept-Language: es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:4201
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Credentials: true
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
content-length: 0

GET /trainings/exercises HTTP/1.1
Host: localhost:8762
Connection: keep-alive
Accept: application/json, text/plain, */*
Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4bGpIN2tlaGJWeWZNcVBPWUgyZTZJbmRQQjZfQm15amtzOEk5dnJtWU8wIn0.eyJleHAiOjE1OTAzNDI1MzMsImlhdCI6MTU5MDM0MjIzMywiYXV0aF90aW1lIjoxNTkwMzQyMTc0LCJqdGkiOiJlZmVhZDI1ZS1jNTI3LTQxMzEtODQyMy1kMWExNGM4NmZiYTgiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODIvYXV0aC9yZWFsbXMvYmFza2VpdG9yIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVkMDRhYWU4LWQ5MTEtNGQ5MC1iMDIyLWU3ZDRkZWExNTRiYyIsInR5cCI6IkJlYXJlciIsImF6cCI6InRlYW1zLXBvcnRhbCIsIm5vbmNlIjoiZjYwOTE0MjUtYzYzMy00MWI3LTkwNGUtZGQ1YWY0MDY2Y2RhIiwic2Vzc2lvbl9zdGF0ZSI6IjJkZTQwNzg3LTZkZGItNGI1ZC05OTVhLTc5MTU0ZGQwMWNmOSIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDo0MjAxIiwiaHR0cDovL2xvY2FsaG9zdDo0MjAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiY29hY2giXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJ0ZXN0MSB0ZXN0MSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QxIiwiZ2l2ZW5fbmFtZSI6InRlc3QxIiwiZmFtaWx5X25hbWUiOiJ0ZXN0MSIsImVtYWlsIjoidGVzdDFAaW52ZW50LmNvbSJ9.jll1KSMZiubWrewXZ_DgtmxeRsHZ38VtDMAXfzMqo87M0C9OQ9ieG6VgveE5M2zJJDuUYv2ixiiOEgZeQ2MOYcZ93YX38viT8KahqE1RggSS2Iiq_O2xLx_BA-GvYtM4D5dkQvfXJ5NQ6id53QRPPKA7T_4lyAafxlmMrtslXOeC6_iGnbWlRxYAeGURoAkAoy8fZhHDbz8MRJ3ayyNpWbgkymQE8ZRw-d8N41n0eapId3UYlEUGRxPtHcbDGZIdPoVgewrIJSFr-Q7oLSlqaTvqbSjFn_zxiVIYjV-c7SVBxeOV5aMebKzCzmiRGYBm-4sFZpa6YJrlbRLnGIFquQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37
Origin: http://localhost:4201
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:4201/
Accept-Encoding: gzip, deflate, br
Accept-Language: es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

HTTP/1.1 302 Found
Location: /oauth2/authorization/keycloak
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
content-length: 0

令牌是使用带有 clientId=teams-portal 的 Keycloaks 登录页面生成的,然后在发送到网关的下一个请求中将输出令牌作为 Bearer 令牌注入。

网关属性

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: training-service
          uri: http://localhost:8200
          predicates:
            - Path=/trainings/**
          filters:
            - StripPrefix=1
            - TokenRelay=
            - RemoveRequestHeader=Cookie
  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: http://localhost:8082/auth/realms/baskeitor
            user-name-attribute: preferred_username
        registration:
          keycloak:
            client-id: gateway
            client-secret: 87444049-832b-41d0-995f-1ae84db61684

培训服务属性

spring.application.name=training-service

keycloak.auth-server-url=http://localhost:8082/auth/
keycloak.realm=baskeitor
keycloak.resource=training-service
keycloak.public-client=true
keycloak.bearer-only=true
keycloak.principal-attribute=preferred_username

角度环境

 keycloakConfig: 
    url: 'http://localhost:8082/auth',
    realm: 'baskeitor',
    clientId: 'teams-portal'
  

¿为什么令牌无效并将我重定向到 /oauth2/authorization/keycloak? ¿我应该如何更改配置才能正常工作?

编辑 1 我已使用此 CORS 配置更新网关:

@EnableWebFlux
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
class CorsConfig : WebFluxConfigurer 
    override fun addCorsMappings(corsRegistry: CorsRegistry) 
        corsRegistry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .maxAge(3600)
    

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    fun corsFilter(): CorsWebFilter 

        val config = CorsConfiguration()
        config.allowCredentials = true
        config.addAllowedOrigin("*")
        config.addAllowedHeader("*")
        config.addAllowedMethod("*")
        val source = UrlBasedCorsConfigurationSource()
        source.registerCorsConfiguration("/**", config)
        return CorsWebFilter(source)
    

但是浏览器显示这个错误:

Access to XMLHttpRequest at 'http://localhost:8082/auth/realms/baskeitor/protocol/openid-connect/auth?response_type=code&client_id=gateway&scope=openid%20address%20email%20microprofile-jwt%20offline_access%20phone%20profile%20roles%20web-origins&state=wDesvgfbtxm29q1MfeFoBYtxyXf-12Nnm47kXR7uzjU%3D&redirect_uri=http://localhost:8762/login/oauth2/code/keycloak&nonce=GvepIeC1HXq-xdq6ZBP333zrumwhXTB_KVei74gdndY' (redirected from 'http://localhost:8762/trainings/exercises') from origin 'http://localhost:4201' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

使用此配置,请求仍被重定向到登录页面:

OPTIONS /trainings/exercises HTTP/1.1
Host: localhost:8762
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: http://localhost:4201
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Sec-Fetch-Dest: empty
Referer: http://localhost:4201/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37
Accept-Encoding: gzip, deflate, br
Accept-Language: es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:4201
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Credentials: true
content-length: 0

GET /trainings/exercises HTTP/1.1
Host: localhost:8762
Connection: keep-alive
Accept: application/json, text/plain, */*
Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4bGpIN2tlaGJWeWZNcVBPWUgyZTZJbmRQQjZfQm15amtzOEk5dnJtWU8wIn0.eyJleHAiOjE1OTA0MjMxNDYsImlhdCI6MTU5MDQyMjg0NiwiYXV0aF90aW1lIjoxNTkwNDIwNTMzLCJqdGkiOiIwMjk0NDA0MC00NTJjLTRkNWUtOGQxYS1kOWYwZTMxNDAyMzQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODIvYXV0aC9yZWFsbXMvYmFza2VpdG9yIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVkMDRhYWU4LWQ5MTEtNGQ5MC1iMDIyLWU3ZDRkZWExNTRiYyIsInR5cCI6IkJlYXJlciIsImF6cCI6InRlYW1zLXBvcnRhbCIsIm5vbmNlIjoiYTI4NjJkYTgtY2Nios00ZDQzLTkxOGEtMGExMmIzYjQ2ZDY0Iiwic2Vzc2lvbl9zdGF0ZSI6IjczMTAxMzNjLTc3MGMtNDdiZS1iZmU5LWZjZDZkODRlZDVlYiIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDo0MjAxIiwiaHR0cDovL2xvY2FsaG9zdDo0MjAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiY29hY2giXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJ0ZXN0MSB0ZXN0MSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QxIiwiZ2l2ZW5fbmFtZSI6InRlc3QxIiwiZmFtaWx5X25hbWUiOiJ0ZXN0MSIsImVtYWlsIjoidGVzdDFAaW52ZW50LmNvbSJ9.LwTs7-FM0Dh2ATx1KwRz5lGfAJjhluvHoTAuiUPd4WXjd1YNgfSQ5R0B_aY05MSLQupiVQNBwrEDmoJT2qup8jyoIUBn4aHRmtS1b3b0ppni60vprHTuEZKpBERfsoGE7mnfyxFv-xL_yUVNOrx4TzM3rnFTIPVchXAQuNkVrnp9UudrdWw4Qar1XhMPZ1J8Df8uPqDr5yBdHXu8u1IPptD3PnfcTfhesx1ugn09JmSFWtQzat7ucmdYjN1buhW8_NCK_kGSblM-k0GheAVO2Fjc6nvigsogW1gmmTRZNB1xX1DSZPNjQ4lNzNAR-JEtP0XEMvGHhuQDJbZ3yvFT6A
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37
Origin: http://localhost:4201
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:4201/
Accept-Encoding: gzip, deflate, br
Accept-Language: es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

HTTP/1.1 302 Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:4201
Access-Control-Allow-Credentials: true
Location: /oauth2/authorization/keycloak
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
content-length: 0

OPTIONS /oauth2/authorization/keycloak HTTP/1.1
Host: localhost:8762
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: http://localhost:4201
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37
Accept-Encoding: gzip, deflate, br
Accept-Language: es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:4201
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Credentials: true
content-length: 0

GET /oauth2/authorization/keycloak HTTP/1.1
Host: localhost:8762
Connection: keep-alive
Accept: application/json, text/plain, */*
Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4bGpIN2tlaGJWeWZNcVBPWUgyZTZJbmRQQjZfQm15amtzOEk5dnJtWU8wIn0.eyJleHAiOjE1OTA0MjMxNDYsImlhdCI6MTU5MDQyMjg0NiwiYXV0aF90aW1lIjoxNTkwNDIwNTMzLCJqdGkiOiIwMjk0NDA0MC00NTJjLTRkNWUtOGQxYS1kOWYwZTMxNDAyMzQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODIvYXV0aC9yZWFsbXMvYmFza2VpdG9yIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVkMDRhYWU4LWQ5MTEtNGQ5MC1iMDIyLWU3ZDRkZWExNTRiYyIsInR5cCI6IkJlYXJlciIsImF6cCI6InRlYW1zLXBvcnRhbCIsIm5vbmNlIjoiYTI4NjJkYTgtY2NiOS00ZDQzLTkxOGEtMGExMmIzYjQ2ZDY0Iiwic2Vzc2lvbl9zdGF0ZSI6IjczMTAxMzNjLTc3MGMtNDdiZS1iZmU5LWZjZDZkODRlZDVlYiIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDo0MjAxIiwiaHR0cDovL2xvY2FsaG9zdDo0MjAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiY29hY2giXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJ0ZXN0MSB0ZXN0MSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QxIiwiZ2l2ZW5fbmFtZSI6InRlc3QxIiwiZmFtaWx5X25hbWUiOiJ0ZXN0MSIsImVtYWlsIjoidGVzdDFAaW52ZW50LmNvbSJ9.LwTs7-FM0Dh2ATx1KwRz5lGfAJjhluvHoTAuiUPd4WXjd1YNgfSQ5R0B_aY05MSLQupiVQNBwrEDmoJT2qup8jyoIUBn4aHRmtS1b3b0ppni60vprHTuEZKpBERfsoGE7mnfyxFv-xL_yUVNOrx4TzM3rnFTIPVchXAQuNkVrnp9UudrdWw4Qar1XhMPZ1J8Df8uPqDr5yBdHXu8u1IPptD3PnfcTfhesx1ugn09JmSFWtQzat7ucmdYjN1buhW8_NCK_kGSblM-k0GheAVO2Fjc6nvigsogW1gmmTRZNB1xX1DSZPNjQ4lNzNAR-JEtP0XEMvGHhuQDJbZ3yvFT6A
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37
Origin: http://localhost:4201
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

HTTP/1.1 302 Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:4201
Access-Control-Allow-Credentials: true
Location: http://localhost:8082/auth/realms/baskeitor/protocol/openid-connect/auth?response_type=code&client_id=gateway&scope=openid%20address%20email%20microprofile-jwt%20offline_access%20phone%20profile%20roles%20web-origins&state=wDesvgfbtxm29q1MfeFoBYtxyXf-12Nnm47kXR7uzjU%3D&redirect_uri=http://localhost:8762/login/oauth2/code/keycloak&nonce=GvepIeC1HXq-xdq6ZBP333zrumwhXTB_KVei74gdndY
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
Set-Cookie: SESSION=d6617ced-7cda-414e-8f7b-2c1f2adb5694; Path=/; HttpOnly; SameSite=Lax
content-length: 0

【问题讨论】:

【参考方案1】:

我认为你把事情搞混了。使用您当前的配置,您需要在网关后面提供或访问 Angular 应用程序。您需要在网关中添加另一个路由,将请求转发到您的 Angular 应用程序并通过网关访问 Angular 应用程序。在您的情况下,它是http://localhost:8762/angularapp。您将被重定向到您的 IdP 以登录并重定向回您的 Angular 应用程序,您将能够访问您的 api。您还需要删除 Angular 应用程序中的 OIDC 身份验证流程/隐式,因为您已经通过身份验证,因此不再需要它。当您访问 API 时,您的网关和 IdP 之间将进行令牌交换,您将获得可在 API 中使用的 JWT 响应。

注意:您的 keycloak 客户端(网关)应设置为机密

如果您不想在网关后面提供 Angular 应用程序。您需要删除网关中的 oauth2 客户端,然后将请求与访问令牌一起转发到您的 api 中,然后让 spring 处理 jwt 以进行授权。 这是tutorial

【讨论】:

以上是关于Keycloak + Spring Cloud Gateway + Angular 9的主要内容,如果未能解决你的问题,请参考以下文章

带有 keycloak 的 Spring Cloud 微服务

spring api gateway 不会将我重定向到 keycloak 提供的 spring-cloud-gateway-client url

尽管设置了承载令牌,但 Spring Cloud Gateway 重定向到 Keycloak 登录页面

如何使用 Keycloak 保护 Angular 8 前端和使用网关、eureka 的 Java Spring Cloud 微服务后端

仅使用承载保护 Spring Cloud Gateway

spring-cloud-starter-consul-config 和 spring-cloud-consul-config 区别是什么?从网上没有搜到答案,待跟进。