Search code examples
spring-bootspring-securitykeycloakspring-security-oauth2spring-cloud-gateway

Getting Access Token with Spring Cloud Gateway and Spring Security with Keycloak


I am using Spring cloud Gateway with Spring security and Keycloak for Access management. I am having an issue getting the access token with spring cloud gateway as the token I am getting doesn't have all the parameters like what I get from the token endpoint in keycloak.

When I hit the keycloak token endpoint with all the details:

http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token

{
  "exp": 1595310135,
  "iat": 1595309835,
  "jti": "0a78d67c-878c-468c-8d03-e003af0350c3",
  "iss": "http://localhost:8080/auth/realms/myrealm",
  "aud": "account",
  "sub": "5c3c71c8-4682-4cd8-8e28-ee66a7edea4e",
  "typ": "Bearer",
  "azp": "myclient",
  "session_state": "af8acabc-b9fb-4d15-9160-b9c613007075",
  "acr": "1",
  "allowed-origins": [
    "http://localhost:8080"
  ],
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "profile email",
  "email_verified": true,
  "name": "Vijay 123",
  "preferred_username": "vijay",
  "given_name": "Vijay",
  "family_name": "123"
}

But at the same time when I try to get the same token from my Spring Cloud Gateway Configuration I get following token:

{
  "exp": 1595244254,
  "iat": 1595243954,
  "auth_time": 1595243954,
  "jti": "6d76736d-51d4-4ae7-9c15-55fc2cf9d96a",
  "iss": "http://localhost:8080/auth/realms/myrealm",
  "aud": "myclient",
  "sub": "5c3c71c8-4682-4cd8-8e28-ee66a7edea4e",
  "typ": "ID",
  "azp": "myclient",
  "session_state": "dfbeb8a3-5d8e-4750-b8af-3dd00105cafa",
  "acr": "1",
  "upn": "vijay",
  "email_verified": true,
  "address": {},
  "name": "Vijay 123",
  "groups": [
    "offline_access",
    "uma_authorization"
  ],
  "preferred_username": "vijay",
  "given_name": "Vijay",
  "family_name": "123"
}

Below are the configuration I have used:

  @Bean
  public GlobalFilter customGlobalFilter() {
    return (exchange, chain) -> exchange.getPrincipal().map(principal -> {

      String token = "";
      String session = "";
      if (principal instanceof OAuth2AuthenticationToken) {
        // For Getting token from request
        SecurityContextImpl context =
            exchange.getSession().block().getAttribute("SPRING_SECURITY_CONTEXT");
        DefaultOidcUser principal1 = (DefaultOidcUser) context.getAuthentication().getPrincipal();
        token = principal1.getIdToken().getTokenValue();
      }
  }

The token value does not match the token value from keycloak.

Is there any other way of getting the OIDC token from the request instead of the OAuth2 token?

I could see the principle is of type OAuth2AuthenticationToken.


Solution

  • I got the oauth2 access token by creating a custom configuration class.

    Step 1: Get the instance of ServerOAuth2AuthorizedClientRepository Interface

    @Autowired
    private ServerOAuth2AuthorizedClientRepository clientRegistrationRepository;
    

    Step 2: Apply Global filter and fetch the token from the OAuth2AuthorizedClient

    // For Getting token from request
    SecurityContextImpl context =
        exchange.getSession().toProcessor().block().getAttribute("SPRING_SECURITY_CONTEXT");
    
    OAuth2AuthenticationToken oauthToken =
        (OAuth2AuthenticationToken) context.getAuthentication();
    
    Mono<OAuth2AuthorizedClient> authorizedClient = clientRegistrationRepository
        .loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(),
            context.getAuthentication(), exchange);
    
    OAuth2AuthorizedClient client = authorizedClient.toProcessor().block();
    
    String accessToken = client.getAccessToken().getTokenValue();
    
    LOG.info("Access Token value: {}", accessToken);
    

    Here is the complete configuration class which does the job perfectly.

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.context.SecurityContextImpl;
    import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
    import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
    import reactor.core.publisher.Mono;
    
    @Configuration
    public class GatewayConfig {
    
      private static final Logger LOG = LoggerFactory.getLogger(GatewayConfig.class);
    
      @Autowired
      private ServerOAuth2AuthorizedClientRepository clientRegistrationRepository;
    
      @SuppressWarnings("deprecation")
      @Bean
      public GlobalFilter customGlobalFilter() {
        return (exchange, chain) -> exchange.getPrincipal().map(principal -> {
          if (principal instanceof OAuth2AuthenticationToken) {
            // For Getting token from request
            SecurityContextImpl context =
                exchange.getSession().toProcessor().block().getAttribute("SPRING_SECURITY_CONTEXT");
    
            OAuth2AuthenticationToken oauthToken =
                (OAuth2AuthenticationToken) context.getAuthentication();
    
            Mono<OAuth2AuthorizedClient> authorizedClient = clientRegistrationRepository
                .loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(),
                    context.getAuthentication(), exchange);
    
            OAuth2AuthorizedClient client = authorizedClient.toProcessor().block();
    
            String accessToken = client.getAccessToken().getTokenValue();
    
            LOG.info("Access Token value: {}", accessToken);
          }
          return exchange;
        }).flatMap(chain::filter).then(Mono.fromRunnable(() -> {
        }));
      }
    }