Configurando RestTemplate para fazer requisições SSL (HTTPS)

Por Carlos Henrique Dos Santos Rodrigues em 04 de abril de 2024

É recorrente a necessidade de realizarmos requisições a aplicações de terceiros que utilizem o HTTPS para criptografar as informações que irão trafegar na rede.

Neste exemplo, iremos mostrar como configurar o suporte SSL (HTTPS) para uma aplicação SpringBoot que utiliza o Resttemplate como meio de comunicação.

Criação do certificado

Iremos utilizar o JDK Keytool para gerar o certificado no formato PKCS12.

O comando a seguir, criará um certificado de nome devrodrigues.p12 com a validade de 365 dias dentro do diretório corrente.

keytool -genkeypair -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore devrodrigues.p12 -validity 365

O comando gera a uma interação no shell, preencha com os valores mais adequados a cada pergunta.

Enter keystore password: 123456
Re-enter new password: 123456
What is your first and last name?
[Unknown]:  DevRodrigues
What is the name of your organizational unit?
[Unknown]:  DevRodrigues
What is the name of your organization?
[Unknown]:  DevRodrigues
What is the name of your City or Locality?
[Unknown]:  Brazil
What is the name of your State or Province?
[Unknown]:  SaoPaulo
What is the two-letter country code for this unit?
[Unknown]:  SP
Is CN=DevRodrigues, OU=DevRodrigues, O=DevRodrigues, L=Brazil, ST=SaoPaulo, C=SP correct?
[no]:  yes

Ao finalizar essa operação o certificado devrodrigues.p12 é criado no diretório atual.

Configuração da aplicação em SpringBoot

Adicione o certificado na sua aplicação

Copei o certificado gerado na etapa anterior e cole dentro do diretório resources da sua aplicação.

Adicione as seguintes dependências no seu projeto

<dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpcore</artifactId>
</dependency>

<dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
</dependency>

Configure as propriedades da aplicação

Adicione as seguintes configurações no arquivo application.properties.

# SSL
server.ssl.key-store=classpath:devrodrigues.p12
server.ssl.key-store-password=123456
server.ssl.keyStoreType=PKCS12

server.port=8443

#RESTTEMPLATE
resttemplate.readTimeout=150000
resttemplate.connectionTimeout=150000
resttemplate.password=123456
resttemplate.protocol=TLS

Encapsulando as properties num objeto Java

Essa classe tem como objetivo, encapsular as properties relacionadas a configuração do RestTemplate, já as configurações do certificado vamos injetar utilizando a anotação @Value, assim você acaba conhecendo formas diferentes de injetar o valor de uma property num objeto Java.

@Component
@ConfigurationProperties("resttemplate")
public class ResttemplateProperties {

   private Integer readTimeout;
   private Integer connectionTimeout;
   private String password;
   private String protocol;

   public ResttemplateProperties() {
   }

   public Integer getReadTimeout() {
       return readTimeout;
   }

   public Integer getConnectionTimeout() {
       return connectionTimeout;
   }

   public String getPassword() {
       return getPassword();
   }

   public String getProtocol() {
       return protocol;
   }
}

Configurando o Bean do RestTemplate

Agora iremos adicionar o nosso certificado ao Resttemplate:

import org.springframework.beans.factory.annotation.Value;@Configuration
public class ResttemplateConfiguration {

   @Value("{server.ssl.key-store}")
   private String keyStoreFile;

   @Value("{server.ssl.keyStoreType}")
   private String keyStoreType;

   private final ResttemplateProperties properties;

   public ResttemplateConfiguration(ResttemplateProperties properties) {
       this.properties = properties;
   }

   @Bean
   public RestTemplate restTemplate() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
       KeyStore clientStore = KeyStore.getInstance(keyStoreType);
       File file = ResourceUtils.getFile(keyStoreFile);
       clientStore.load(new FileInputStream(file), properties.getPassword().toCharArray());

       SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
       sslContextBuilder.setProtocol(properties.getProtocol());
       sslContextBuilder.loadKeyMaterial(clientStore, properties.getPassword().toCharArray());
       sslContextBuilder.loadTrustMaterial(new TrustSelfSignedStrategy());

       SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build());
       CloseableHttpClient httpClient = HttpClients
               .custom()
               .setSSLSocketFactory(sslConnectionSocketFactory)
               .build();

       HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
       requestFactory.setConnectTimeout(properties.getConnectionTimeout());
       requestFactory.setReadTimeout(properties.getReadTimeout());

       return new RestTemplate(requestFactory);
   }
}

Prontinho! Com os passos acimas executados nossa aplicação já está configurada para realizar chamadas a endpoints seguros.

Testando nossa implementação

Para o primeiro teste vamos rodar a aplicação e acessar o seguinte endereço no navegador https://localhost:8443 e visualizar as informações do certificado conforme imagem a seguir.

1
Figure 1. Detalhes do certificado https

No segundo teste vamos fazer uma requisição a um API publica do Senado utilizando o Bean do RestTemplate criado nas etapas anteriores.

@Service
public class RequestGateway {

   private final RestTemplate restTemplate;

   public RequestGateway(RestTemplate restTemplate) {
       this.restTemplate = restTemplate;
   }

   public void mustPerformHttpsCall() {
       executeRequest(() -> {
           var url = "https://legis.senado.leg.br/dadosabertos/materia/assuntos?indAtivos=S";
           var uri = convertToUri(url);

           ResponseEntity<String> response = restTemplate.exchange(
                   uri,
                   HttpMethod.GET,
                   null,
                   String.class);

           // resposta ignorada
       });
    }

    private void executeRequest(Runnable runnable) {
       try {
           runnable.run();
       } catch (HttpClientErrorException | HttpServerErrorException e) {
           // trate o erro da forma que achar mais conveniente para sua aplicação
           e.printStackTrace();
       }
    }

    private String convertToUri(String url) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
        return builder.toUriString();
    }
}

Poderiamos utilizar um teste unitário para realizar uma chamado a classe acima. Mas resolvemos utilizar a implementação de CommandLineRunner que é uma interface funcional a qual é executada assim que o container do SpringBoot for inicializado.

@SpringBootApplication
public class HttpsspringbootApplication implements CommandLineRunner {

   private final RequestGateway gateway;

   public HttpsspringbootApplication(RequestGateway gateway) {
       this.gateway = gateway;
   }

   public static void main(String[] args) {
       SpringApplication.run(HttpsspringbootApplication.class, args);
   }

   @Override
   public void run(String... args) throws Exception {
       gateway.mustPerformHttpsCall();
   }
}

Por fim, o código fonte dessa aplicação esta no repositório hospedado no GitHub.

// Compartilhe esse Post