Ir para o conteúdo

Neste artigo serão vistas duas formas de realizar a recuperação do token JWT fornecido via Keycloak para envio de requisições internas entre duas aplicações em Java utilizando Spring.

Vale ressaltar que o foco não é destrinchar sobre conceitos e funcionamento do Keycloak, mas mostrar de forma clara e direta a implementação para recuperação e envio do token entre serviços.

Keycloak: o que é?

O Keycloak é um produto de software de código aberto escrito em Java e licenciado pela RedHat que permite o login único com Gerenciamento de Identidade e Acesso voltado para aplicativos e serviços modernos[1].

Para saber mais sobre a ferramenta, acesse o link [1] nas referências.

Como funciona o Feign?

Uma das formas mais elegantes e simples de se comunicar com outros serviços Rest é criando uma interface com o FeignClient.

O Feign funciona processando anotações em uma solicitação padronizada, cujos argumentos são aplicados a esses modelos de maneira direta antes da saída. Ele simplifica drasticamente os aspectos do sistema, como a repetição de solicitações. Além disso, o Feign facilita o teste unitário de suas conversões[2].

Uma das facilidades ao fazer uso do Feign é permitir configurá-lo através de uma classe de configuração. Para o proposto aqui, será feito o uso desta classe.

Direto ao ponto

Existem algumas maneiras de obter o token JWT gerado pelo Keycloak, entre elas temos uma através da biblioteca Java keycloak-admin-client e outra por meio do HttpClient do Java, fazendo assim uma chamada REST. Vale ressaltar que nos exemplos abaixo utilizaremos Java 11.

É possível aproveitar o token da chamada REST original para ser enviado nas chamadas REST internas para outros serviços desde que estejam no mesmo REALM e regras configuradas no Keycloak. Como mencionado acima, os conceitos e funcionamento da ferramenta não serão o centro deste artigo.

Agora, imagine que não existe uma chamada REST para um determinado serviço que chame outro, logo não existe um token para reaproveitá-lo. Um exemplo clássico se trata de Jobs, que internamente chama um serviço REST externo à aplicação. Como então recuperar o token para enviar nesta chamada, realizando assim a autenticação e autorização, e obter um determinado resultado?

Uma das formas é através da biblioteca java keycloak-admin-client.

Obtendo Token através da Biblioteca

1- No seu pom.xml adicione a dependências do Keycloak.

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-admin-client</artifactId>
    <version>${keycloak-admin.version}</version>
</dependency>
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-spring-boot-starter</artifactId>
    <version>${keycloak-boot.version}</version>
</dependency>

2- Adicione em seu application todas as informações necessárias para comunicação com o Keycloak.

# Security
spring.security.oauth2.resourceserver.jwt.issuer-uri=${BACKEND_AUTH_SERVER_URL}
security.oauth2.client.access-token-uri=${BACKEND_ACCESS_TOKEN_URL}
security.oauth2.client.client-id=${BACKEND_CLIENT_ID}
security.oauth2.client.realm=${BACKEND_REALM}
security.oauth2.client.client-secret=${BACKEND_CLIENT_SECRET}
security.oauth2.client.grant-type=client_credentials

3- Crie uma classe de serviço que retornará o token gerado no keycloak.

@Service
@RequiredArgsConstructor
@Slf4j
public class KeycloakService {
    private static final String BEARER = "Bearer ";

    @Value("${security.oauth2.client.access-token-uri}")
    private String uri;
    @Value("${security.oauth2.client.client-id}")
    private String clientId;
    @Value("${security.oauth2.client.realm}")
    private String realm;
    @Value("${security.oauth2.client.client-secret}")
    private String secret;

    public String fetchToken() {
        log.debug("Executando autenticação");
        String token;

        try {
            Keycloak keycloak = KeycloakBuilder.builder()
                .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                .serverUrl(uri)
                .realm(realm)
                .clientId(clientId)
                .clientSecret(secret)
                .build();
            
            token = keycloak.tokenManager().getAccessTokenString();
        } catch (Exception e) {
            log.warn("Erro ao retornar token de acesso via keycloak.");
            throw new ServiceUnavailableException();
        }

        log.debug("Token de acesso recebido: {}", token);

        return format("%s%s", BEARER, token);
    }
}

4- Em seguida, crie uma classe de configuração, criando um @bean que retorne um RequestInterceptor para envio do token na requisição[3].

@Configuration
@RequiredArgsConstructor
@Slf4j
public class FeignConfig {
    private static final String AUTHORIZATION = "Authorization";

    private final KeycloakService keycloakService;

    @Bean
    public RequestInterceptor requestKeycloakInterceptor() {
        log.debug("Realizando autenticação keycloak com serviços internos para chamada via Feign Client");

        return requestTemplate -> {

            final var token = keycloakService.fetchToken();

            requestTemplate.header(AUTHORIZATION, token);
        };
    }
}

5- Por fim, adicione a classe de configuração no parâmetro ‘configuration’ da interface.

@FeignClient(name = "feign-manager", url = "${feign.manager.url}", configuration = FeignConfig.class)
public interface ManagerClient {

    @PostMapping("/v1/exemplo")
    Object create(@RequestBody Object request);
}

Obtendo Token através de HttpClient

Basicamente, para obter o token via HttpClient implementado no Java 11, será necessário alterar apenas a classe de serviço, e não será adicionada as dependências do Keycloak como visto no exemplo anterior. Também criamos um objeto chamado KeycloakToken para receber o token do response.

Para saber mais informações sobre HttpClient basta acessar o link da referência[4].

@Data
@Builder
@AllArgsConstructor
public class KeycloakToken {

    @JsonProperty("access_token")
    private final String accessToken;
}
@Service
@RequiredArgsConstructor
@Slf4j
public class KeycloakService {
    private static final String GRANT_TYPE = "grant_type";
    private static final String CLIENT_ID = "client_id";
    private static final String CLIENT_SECRET = "client_secret";
    private static final String BEARER = "Bearer ";

    @Value("${security.oauth2.client.access-token-uri}")
    private String uri;
    @Value("${security.oauth2.client.client-id}")
    private String clientId;
    @Value("${security.oauth2.client.client-secret}")
    private String secret;
    @Value("${security.oauth2.client.grant-type}")
    private String grantType;

    private final Gson gson;

    private final HttpClient client;

    public String fetchToken() {
        log.debug("Executando autenticação");

        var urlParameters = format("%s=%s&%s=%s&%s=%s", GRANT_TYPE, grantType, CLIENT_ID, clientId, CLIENT_SECRET, secret);

        HttpResponse<String> response;

        try {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI(uri))
                .POST(HttpRequest.BodyPublishers.ofString(urlParameters))
                .headers("Content-Type", "application/x-www-form-urlencoded")
                .build();
            response = client.send(request, HttpResponse.BodyHandlers.ofString());
        } catch (InterruptedException e) {
            log.warn("Erro ao retornar token de acesso via keycloak. Thread interrompida.");
            Thread.currentThread().interrupt();
            throw new ServiceUnavailableException();
        } catch (Exception e) {
            log.warn("Erro ao retornar token de acesso via keycloak.");
            throw new ServiceUnavailableException();
        }

        var token = gson.fromJson(response.body(), KeycloakToken.class);

        log.debug("Token de acesso recebido: {}", token.getAccessToken());

        return format("%s%s", BEARER, token.getAccessToken());
    }
}

Como visto acima, primeiro foi criada a classe que receberá o token de acesso e em seguida, no service, um request foi construído para ser enviado através do HttpClient.

Conclusão

O objetivo deste artigo foi mostrar de forma direta como obter o Token via Keycloak e enviá-lo para requisições entre serviços que se comunicam via Feign. Para tal, foi utilizado duas formas: HttpClient, fornecido pelo Java 11, e a biblioteca keycloak-admin-client. É perceptível que fazer uso da biblioteca deixa o código mais limpo e fácil de ser lido, mas é importante entender o que melhor se adequa para a sua aplicação.

Referências

[1] https://www.keycloak.org/

[2] https://www.baeldung.com/java-9-http-client

[3] https://www.baeldung.com/spring-cloud-feign-oauth-token

[4] https://openjdk.org/groups/net/httpclient/intro.html

[5] https://github.com/OpenFeign/feign

[6] https://www.baeldung.com/spring-cloud-openfeign

[7] https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

Outras publicações