ch4mpy / spring-addons

Ease spring OAuth2 resource-servers configuration and testing
Apache License 2.0
521 stars 84 forks source link

BFF configuration token is not refreshed #187

Closed glennvdv closed 6 months ago

glennvdv commented 6 months ago

Describe the bug I have spring cloud gateway + addons in my configuration. I notice that my token is not refreshed before sending it downstream. I debugged and found out that the default GatewayReactiveOAuth2AutoConfiguration is not used (which has token refresh enabled), because a bean of type PerRegistrationReactiveOAuth2AuthorizedClientProvider is configured. This last one lacks the refresh ability.

I'm i missing a configuration or is this indeed a bug? dependencies

 implementation(platform(libs.spring.cloud.bom))
    implementation(libs.spring.boot.oauth2.client)
    implementation(libs.spring.boot.oauth2.resourceserver)
    implementation(libs.spring.cloud.gateway)
    implementation(libs.spring.addons.oidc)
    implementation(libs.spring.session.redis)
    implementation(libs.spring.boot.redis)
    implementation(libs.spring.boot.actuator)

    runtimeOnly(libs.micrometer.prometheus)

    annotationProcessor(libs.spring.boot.annotation)
    developmentOnly(libs.spring.boot.dev)

application,yaml

bff:
  issuer: http://localhost:8081/realms/changeme
  client-id: "changeme"
  client-secret: "changeme"
  user-name-attribute: preferred_username
  gateway-uri: http://localhost:${server.port}
  api-uri: http://localhost:8082
  ui-uri: http://localhost:5173

server:
  port: 8080
  ssl:
    enabled: false
  reactive:
    session:
      cookie:
        name: BFF_SESSION_ID
        http-only: true
        sameSite: Strict
        secure: true  #still works for http://localhost

spring:
  codec:
    max-in-memory-size: 1MB

  ####  Session in memory ####
  session:
    store-type: none
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: ${bff.issuer}
            user-name-attribute: ${bff.user-name-attribute}
        registration:
          keycloak:
            provider: keycloak
            client-id: ${bff.client-id}
            client-secret: ${bff.client-secret}
            authorization-grant-type: authorization_code
            scope:
              - openid
              - profile
              - email
              - offline_access
              - roles
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
        - SaveSession
      routes:
        # api access to backend API (with TokenRelay replacing session cookies with access tokens)
        # To be used by SPAs
        - id: api
          uri: ${bff.api-uri}
          #/api is routed to /api backend
          predicates:
            - Path=/api/**
          filters:
            - TokenRelay=
#            - StripPrefix=1
        - id: ui
          uri: ${bff.ui-uri}
          #/ui is routed to / frontend
          predicates:
            - Path=/**

com:
  c4-soft:
    springaddons:
      oidc:
        ops:
          - iss: ${bff.issuer}
            authorities:
              - path: $.realm_access.roles
            username-claim: ${bff.user-name-attribute}
        client:
          # Access to resources protected by oauth2
          client-uri: ${bff.gateway-uri}
          security-matchers:
            - /**
          permit-all:
            - /favicon.ico
            - /actuator/health
            - /actuator/info
            - /api/actuator/info
            - /api/actuator/health
          cors:
            - path: /api/**
              allowed-origin-patterns:
                - ${bff.gateway-uri}
                - https://localhost/
          csrf: cookie-accessible-from-js
          post-login-redirect-path: /
          post-logout-redirect-path: /
          oauth2-redirections:
            rp-initiated-logout: accepted

management:
  endpoint:
    gateway:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "health,info,gateway"

---
spring:
  config:
    activate:
      on-profile: redis

  autoconfigure:
    exclude: []
  ###  Session with redis ####
  session:
    store-type: redis
    redis:
      port: 6379

---
spring:
  config:
    activate:
      on-profile: ssl

scheme: https
server:
  ssl:
    enabled: true

---
spring:
  config:
    activate:
      on-profile: mobile
com:
  c4-soft:
    springaddons:
      oidc:
        client:
          oauth2-redirections:
            pre-authorization-code: NO_CONTENT
            post-authorization-code: NO_CONTENT

# gateway-uri: ${scheme}://10.0.2.2:${server.port}
ch4mpy commented 6 months ago

@glennvdv thanks for reaching out with your detailed dependencies and configuration.

Configuring a refresh-token client manager is a default not only for spring-cloud-gateway auto-configuration: spring-boot-starter-oauth2-client does it and it was intended that PerRegistrationReactiveOAuth2AuthorizedClientProvider does it too (but it was poorly implemented).

So yes, this is a bug and I think I spotted it: around line 50, the new RefreshTokenReactiveOAuth2AuthorizedClientProvider() should be added at the same time the authorization-code one is added (at least if offline_access scope is present).

Maybe, all the providers should be added in case some new client registrations are added at runtime. Let's say we have only client-credentials registrations at configuration time and add registrations with authorization-code after the app started. With current logic, the client-providers for authorization-code and refresh-token won't be registered and things will go very bad.

Will fix soon (probably within the next 24h).

ch4mpy commented 6 months ago

@glennvdv This should be fixed in 7.5.3 which is available from maven central already.

Would you please confirm and close?

glennvdv commented 6 months ago

fix confirmed