Creation of an authorization service through the ESIA system

Hi all. My name is Dinis, I am a senior developer at BFT-Holding.

This article provides a plan for creating an authorization service through the ESIA system. The topic will be of interest to those who implement such a service for their application. In the article I have collected key excerpts from the documentation with parts of the code, and you can find all the code at the end of the article via the GitHub link.

What is ESIA?

ESIA is a unified identification and authentication system. It generates, records and stores information about system participants – individuals and legal entities who have registered and created an account. A confirmed account in the ESIA is a kind of electronic passport, with which you can access various sites, portals and systems without using additional registration and authorization means, such as a login and password.


The ESIA service has excellent documentation, so most of this article contains excerpts from it – they are in italics. The full version of the documentation is available at the link: https://digital.gov.ru/ru/documents/6182/


Now – to the most interesting part.

1. Creating a Java project.

First, let’s add these dependencies:

As a crypto provider we will use the Russian crypto provider Vipnet, certified by the FSB of Russia as a means of cryptographic protection of information and electronic signatures.

Download from the site (https://infotecs.ru/products/vipnet-jcrypto-sdk/) crypto provider libraries. Let’s add them to the jcr/lib directory at the root of the project.

Let’s add dependencies to build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.15'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'ru.habr'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '1.8'
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2021.0.8")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.cloud:spring-cloud-starter'
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'

    compileOnly 'org.projectlombok:lombok:1.18.22'
    annotationProcessor 'org.projectlombok:lombok:1.18.22'

    // Для работы с шифрованием по ГОСТ.
    implementation files('jcr/lib/jcrypto-jca-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-ocsp-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-pkcs11-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-pkcs7-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-smime-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-ssl-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-widgets-fx-2.8.6-R42.jar')
    implementation files('jcr/lib/jcrypto-xmldsig-2.8.6-R42.jar')
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

Let’s fill out our application.yml:

server:
  port: 8081

spring:
  application:
    name: esia


habr:
  esia:
    scope: fullname birthdate gender snils id_doc email contacts kid_fullname kid_birthdate kid_gender kid_snils kid_inn kid_birth_cert_doc kid_medical_doc
    clientId: FSS-ESN
    ketStorageDirectory: /home/esia/eiis_keys
    ketStoragePassword: password
    ketFile:  pkiClient-container
    host: esia-portal1.test.gosuslugi.ru
    baseUrl: https://esia-portal1.test.gosuslugi.ru/
    authCodeUlr: https://esia-portal1.test.gosuslugi.ru/aas/oauth2/ac
    redirectUrl: http://127.0.01:8081

Let’s configure the web client:

@Configuration
public class WebClientConfiguration {

    @Value("${habr.esia.baseUrl}")
    private String baseUrl;
    public static final int TIMEOUT = 1000;

    @Bean
    public WebClient webClientWithTimeout() {
        final TcpClient tcpClient = TcpClient
                .create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, TIMEOUT)
                .doOnConnected(connection -> {
                    connection.addHandlerLast(new ReadTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS));
                    connection.addHandlerLast(new WriteTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS));
                });

        return WebClient.builder()
                .baseUrl(baseUrl)
                .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
                .build();
    }
}

Since we will be working with jwt tokens, we will create the JwtUtil class:

@Service
@Slf4j
public class JwtUtil {

    private ObjectMapper mapper;

    @PostConstruct
    public void init() {
        this.mapper = new ObjectMapper();
    }

    public Map<String, Object> getTokenData(String token) {
        token = this.withoutBearerToken(token);
        String[] parts = token.split("\\.");
        String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);

        Map<String, Object> jsonMap = new HashMap<String, Object>();
        try {
            // convert JSON string to Map
            jsonMap = this.mapper.readValue(payload,
                    new TypeReference<Map<String, Object>>() {
                    });
        } catch (Exception ex) {
            log.error("Ошибка парсинга токена: {}", ex.getMessage());
            throw new RuntimeException(ex);
        }
        return jsonMap;
    }

    public String getUserOid(String token) {
        return  String.valueOf(getTokenData(token).get("urn:esia:sbj_id"));
    }

    public String withoutBearerToken(String token) {
        if (token.startsWith("Bearer ")) {
            return token.substring(7);
        }
        return token;
    }

    public String withBearerToken(String token) {
        if (!token.startsWith("Bearer ")) {
            return String.format("Bearer %s", token);
        }
        return token;
    }
}
  1. Implementation of an authorization service.

General principles

This access control model is used in cases where the client system, when accessing a resource, must obtain permission for this action from the owner of the resource.

In general, the interaction scheme looks like this:

  • The client system requests permission from the resource owner to access the appropriate resources. Typically, this request is made not directly to the resource owner, but through an authorization service (which, in turn, requests permission from the resource owner), since the resource owner himself cannot issue either an access token or an authorization code.

  • The client system receives an authorization grant in the form of an authorization code.

  • The client system requests an access token by presenting an authorization code to the authorization service.

  • The authorization service authenticates the client system, verifies the authorization code, and issues an access token and a refresh token.

  • The client system requests a protected resource from the provider by presenting an access token.

  • The resource provider verifies the access token. If it is valid, it allows access to the protected resource.

  • The client system again requests access to the protected resource using the previously issued token.

  • The resource provider checks the token, finds that it has expired, and returns an error message.

  • The client system contacts the authorization service to obtain a new access token by presenting a refresh token.

  • The authorization service checks the validity of the refresh token and returns two new tokens: accesses and updates.

Once the client system has received an access token, it can make repeated requests to obtain the protected resource until the token expires. When this happens, the client system will need to obtain a new access token.

Method for obtaining an authorization code from a specific URL

To obtain an authorization code, the client system must obtain permission to access the protected resource from its owner. If the owner is an ESIA user, the client system must direct the user to the page for granting access rights to the ESIA (the user must be pre-authenticated in the system, or it will ask him to undergo identification and authentication).

public String getUrl(ServerRequest request) throws EsiaException, UnsupportedEncodingException {
    ClientSecretResponse clientSecretResponse = this.getClientSecret();
    String type = request.pathVariable("redirect");
    String timestampUrlEncoded = getTimestampUrlEncoded(clientSecretResponse);
    String redirectUrlEncoded = getRedirectUrlEncoded(String.format("http://%s:%s/%s",
            request.uri().getHost(),
            request.uri().getPort(),
            type));
    UriComponentsBuilder accessTokenRequestBuilder = UriComponentsBuilder.fromHttpUrl(this.authCodeUlr)
            .queryParam("client_id", URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString()))
            .queryParam("response_type", URLEncoder.encode("code", StandardCharsets.UTF_8.toString()))
            .queryParam("access_type", URLEncoder.encode("offline", StandardCharsets.UTF_8.toString()))
            .queryParam("scope", URLEncoder.encode(scope, StandardCharsets.UTF_8.toString()))
            .queryParam("state", URLEncoder.encode(clientSecretResponse.getState(), StandardCharsets.UTF_8.toString()))
            .queryParam("client_secret", URLEncoder.encode(clientSecretResponse.getClient_secret(), StandardCharsets.UTF_8.toString()));
    String url = accessTokenRequestBuilder.toUriString();
    url += "&timestamp=" + timestampUrlEncoded;
    url += "&redirect_uri=" + redirectUrlEncoded;
    return url;
}

This link contains:

  • – identifier of the client system (mnemonic of the system in the ESIA, indicated in capital letters);

  • – request signature in PKCS#7 detached signature format
    in UTF-8 encoding from the values ​​of four HTTP request parameters: scope, timestamp, clientId, state (without delimiters). must be base64 url ​​safe encoded. The certificate used to verify the signature must be pre-registered in the Unified Automated Identification and Automation and linked to the KZ of the client system in the Unified Identification and Automation. ESIA uses certificates in the X.509 format and interacts with the algorithms for generating an electronic signature GOST R 34.10-2012 and cryptographic hashing GOST R 34.11-2012;

  • – link to which the user should be directed
    after giving permission to access the resource;

  • – access area, i.e. rights requested; for example, if a client system requests access to information about employees of an organization,
    then the access scope (scope) should have the value http://esia.gosuslugi.ru/org_emps (with the necessary parameters);
    if the access scope (scope) id_doc (user data) is requested, then there is no need to specify the oid of this user as a parameter;

  • is the type of response that is expected from the Unified Identification and Automation System, has the value code if the client system must receive an authorization code;

  • – a set of random characters in the form of a 128-bit request identifier (necessary for protection against interception), generated according to the UUID standard;

    – authorization code request time in the format yyyy.MM.dd HH:mm:ss Z (for example, 2013.01.25 14:36:11 +0400), necessary to fix the beginning of the time period during which the request will be valid
    with the given identifier ();

  • – takes the value “offline” if access is required
    to resources and when the owner cannot be called (in which case a refresh token is issued); value “online” – access is required only if the owner is present.

When the authorization code is received, the client system can generate a POST request to the ESIA https address to receive an access token. One authorization code can be exchanged for one access token.

public Mono<LinkedHashMap> openEsiaSession(String code, String state, ServerRequest request) throws EsiaException, IOException {
    ClientSecretResponse clientSecretResponse = this.getClientSecret();
    String timestampUrlEncoded = getTimestampUrlEncoded(clientSecretResponse);
    String redirectUrlEncoded = getRedirectUrlEncoded(String.format("http://%s:%s",
            request.uri().getHost(),
            request.uri().getPort()));
    StringBuilder formData = new StringBuilder("&");
    formData.append(URLEncoder.encode("grant_type", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode("authorization_code", StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("client_id", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("code", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(code, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("state", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getState(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("token_type", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode("Bearer", StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("scope", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(scope, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("refresh_token", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getState(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("client_secret", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getClient_secret(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append("timestamp").append("=").append(timestampUrlEncoded).append("&");
    formData.append(URLEncoder.encode("redirect_uri", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(redirectUrlEncoded, StandardCharsets.UTF_8.toString()));
    
    return webClient
            .post()
            .uri(uriBuilder ->
                    uriBuilder.host(esiaHost)
                            .path("/aas/oauth2/te")
                            .build()
            )
            .header("Content-Type", "application/x-www-form-urlencoded")
            .bodyValue(formData.toString())
            .retrieve()
            .bodyToMono(LinkedHashMap.class)
            .timeout(Duration.ofMillis(30000))
            .onErrorResume(e -> {
                LinkedHashMap<String, Object> errorMap = new LinkedHashMap<>();
                errorMap.put("error", e.getMessage());
                return Mono.just(errorMap);
            })
            .doOnError(error -> {
                log.error("An error has occurred {}", error.getMessage());
                throw new RuntimeException();
            });
}

The request body must include the following information:

  • – identifier of the client system (mnemonic of the system in the ESIA, indicated in capital letters);

  • – the value of the authorization code that was previously received
    from the ESIA, it must be exchanged for an access token;

  • – takes the value “authorization_code”,
    if the authorization code is exchanged for an access token;

  • – request signature in PKCS#7 detached signature format
    in UTF-8 encoding from the values ​​of four HTTP request parameters: scope, timestamp, clientId, state (without delimiters). must be base64 url ​​safe encoded. The certificate used to verify the signature must be pre-registered in the Unified Automated Identification and Automation and linked to the KZ of the client system in the Unified Identification and Automation. ESIA uses certificates in X.509 format and interacts with electronic signature generation algorithms GOST R 34.10-2012
    and cryptographic hashing GOST R 34.11-2012;

  • – a set of random characters in the form of a 128-bit request identifier (necessary for protection against interception), generated according to the UUID standard; this set of characters must be different from the one that was used to obtain the authorization code;

  • – link to which the user should be directed
    after giving permission to access (the same value that was specified in the request for an authorization code);

  • – access area, i.e. requested rights (the same value that was specified in the request for an authorization code);

  • – access scope, i.e. requested rights for legal entities (the same value that was specified in the request for an authorization code);

  • – time of the token request in the format yyyy.MM.dd HH:mm:ss Z (for example, 2013.01.25 14:36:11 +0400), necessary to fix the beginning of the time period during which the request with this data will be valid identifier();

  • – type of the requested token; currently the Unified Identification and Automation System only supports the “Bearer” value. The parameter is optional.

    If the request is successfully verified, the Unified Identification and Information System returns a response in JSON format:

  • – access token for this resource;

  • – time during which the token expires
    (in seconds);

  • – a set of random characters in the form of a 128-bit request identifier, generated according to the UUID standard (matches
    with request ID);

  • – type of the provided token, currently the ESIA only supports the “Bearer” value;

    – refresh token for this resource.

Generating client_secret

public ClientSecretResponse getClientSecret() throws EsiaException {
    try {
        ZonedDateTime now = ZonedDateTime.now();
        String timestamp = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx").format(now);
        String state = UUID.randomUUID().toString();
        String msg = String.format("%s%s%s%s", scope, timestamp, clientId, state);
        byte[] messageAsByte = msg.getBytes(StandardCharsets.UTF_8);
        ByteArrayOutputStream clientSecretOS = new ByteArrayOutputStream();
        try (
                CMSSignedDataOutputStream signedStream = new CMSSignedDataOutputStream(clientSecretOS)) {
            signedStream.addCertificates(certificate);
            signedStream.addSigner(privateKey, certificate);
            signedStream.write(messageAsByte, 0, messageAsByte.length);
        }
        byte[] utf = clientSecretOS.toByteArray();
        String clientSecret = new String(Base64.getEncoder().encode(utf));
        String clientSecretUrlEncoded = clientSecret.replace("+", "-")
                .replace("/", "_")
                .replace("=", "");
        log.debug("Generated new clientSecret:" + clientSecretUrlEncoded);
        return new ClientSecretResponse(timestamp, state, scope, clientSecretUrlEncoded);
    } catch (Exception error) {
        throw new EsiaException(error);
    }
}

If no errors occurred during authorization, then the ESIA redirects the user to the link specified in redirect_uri and also returns two required parameters:

  • – authorization code value;

  • – the value of the state parameter that was received in the request
    for authorization; the client system must compare the sent
    and the received state parameter.

In case of an error, the authorization service will return an error code in the error parameter (for example, “access_denied”) and redirect the user to the address specified
in redirect_uri.

When the authorization code is received, the client system can generate a POST request to the ESIA https address to receive an access token. One authorization code can be exchanged for one access token. The request body must include the following information:

  • – identifier of the client system (mnemonic of the system in the Unified Identification and Logistics System indicated in capital letters);

  • – the value of the authorization code that was previously received
    from the ESIA and which must be exchanged for an access token;

  • – takes the value “authorization_code”,
    if the authorization code is exchanged for an access token;

  • – request signature in PKCS#7 detached signature format
    in UTF-8 encoding from the values ​​of four HTTP request parameters: scope, timestamp, clientId, state (without delimiters). must be base64 url ​​safe encoded. The certificate used to verify the signature must be pre-registered in the Unified Automated Identification and Automation and linked to the KZ of the client system in the Unified Identification and Automation. ESIA uses certificates in X.509 format and interacts with electronic signature generation algorithms GOST R 34.10-2012
    and cryptographic hashing GOST R 34.11-2012;

  • – a set of random characters in the form of a 128-bit request identifier (necessary for protection against interception), generated according to the UUID standard; this set of characters must be different from the one that was used to obtain the authorization code;

  • – link to which the user should be directed
    after giving permission to access (the same value that was specified in the request for an authorization code);

  • – access area, i.e. requested rights (the same value that was specified in the request for an authorization code);

  • – access scope, i.e. requested rights for legal entities (the same value that was specified in the request for an authorization code);

  • – time of the token request in the format yyyy.MM.dd HH:mm:ss Z (for example, 2013.01.25 14:36:11 +0400), necessary to fix the beginning of the time period during which the request with this data will be valid identifier();

  • – type of the requested token; currently the Unified Identification and Automation System only supports the “Bearer” value. The parameter is optional.

public Mono<LinkedHashMap> openEsiaSession(String code, String state, ServerRequest request) throws EsiaException, IOException {
    ClientSecretResponse clientSecretResponse = this.getClientSecret();
    String timestampUrlEncoded = getTimestampUrlEncoded(clientSecretResponse);
    String redirectUrlEncoded = getRedirectUrlEncoded(String.format("http://%s:%s",
            request.uri().getHost(),
            request.uri().getPort()));
    StringBuilder formData = new StringBuilder("&");
    formData.append(URLEncoder.encode("grant_type", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode("authorization_code", StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("client_id", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("code", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(code, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("state", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getState(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("token_type", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode("Bearer", StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("scope", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(scope, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("refresh_token", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getState(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("client_secret", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getClient_secret(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append("timestamp").append("=").append(timestampUrlEncoded).append("&");
    formData.append(URLEncoder.encode("redirect_uri", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(redirectUrlEncoded, StandardCharsets.UTF_8.toString()));

    return webClient
            .post()
            .uri(uriBuilder ->
                    uriBuilder.host(esiaHost)
                            .path("/aas/oauth2/te")
                            .build()
            )
            .header("Content-Type", "application/x-www-form-urlencoded")
            .bodyValue(formData.toString())
            .retrieve()
            .bodyToMono(LinkedHashMap.class)
            .timeout(Duration.ofMillis(30000))
            .onErrorResume(e -> {
                LinkedHashMap<String, Object> errorMap = new LinkedHashMap<>();
                errorMap.put("error", e.getMessage());
                return Mono.just(errorMap);
            })
            .doOnError(error -> {
                log.error("An error has occurred {}", error.getMessage());
                throw new RuntimeException();
            });
}

If the request is successfully verified, the Unified Identification and Information System returns a response in JSON format:

  • – access token for this resource;

  • – time during which the token expires
    (in seconds);

  • – a set of random characters in the form of a 128-bit request identifier, generated according to the UUID standard (matches
    with request ID);

  • – type of the provided token, currently the ESIA only supports the “Bearer” value;

  • – refresh token for this resource.

When using an access token, it is recommended that client systems first check that it has not expired. If the token has expired, you will need to first obtain a new access token using a refresh token to successfully access the protected resource. To do this, the client system should generate a request using the POST method to the Unified Identification Number, which has a structure similar to the initial request to receive a token. Features of request parameter values:

  • – the value of the refresh token available on the client system, which should be exchanged for a new access token (indicated instead of )

  • - must be "refresh_token" because the refresh token is exchanged for an access token

public Mono<LinkedHashMap<String, Object>> updateEsiaSession(String refreshToken, ParameterizedTypeReference<LinkedHashMap<String, Object>> typeReference, ServerRequest request) throws EsiaException, IOException {

    ClientSecretResponse clientSecretResponse = this.getClientSecret();
    String timestampUrlEncoded = getTimestampUrlEncoded(clientSecretResponse);

    String redirectUrlEncoded = getRedirectUrlEncoded(String.format("http://%s:%s",
            request.uri().getHost(),
            request.uri().getPort()));

    StringBuilder formData = new StringBuilder("&");
    formData.append(URLEncoder.encode("client_id", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("client_secret", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getClient_secret(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("refresh_token", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(refreshToken, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("scope", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(scope, StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("grant_type", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode("refresh_token", StandardCharsets.UTF_8.toString())).append("&");
    formData.append(URLEncoder.encode("state", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(clientSecretResponse.getState(), StandardCharsets.UTF_8.toString())).append("&");
    formData.append("timestamp").append("=").append(timestampUrlEncoded).append("&");
    formData.append(URLEncoder.encode("redirect_uri", StandardCharsets.UTF_8.toString())).append("=").append(URLEncoder.encode(redirectUrlEncoded, StandardCharsets.UTF_8.toString()));

    return webClient
            .post()
            .uri(uriBuilder ->
                    uriBuilder.host(esiaHost)
                            .path("/aas/oauth2/te")
                            .build()
            )
            .header("Content-Type", "application/x-www-form-urlencoded")
            .bodyValue(formData.toString())
            .retrieve()
            .bodyToMono(typeReference)
            .timeout(Duration.ofMillis(30000))
            .onErrorResume(e -> {
                LinkedHashMap<String, Object> errorMap = new LinkedHashMap<>();
                errorMap.put("error", e.getMessage());
                return Mono.just(errorMap);
            })
            .doOnError(error -> {
                log.error("An error has occurred {}", error.getMessage());
                throw new RuntimeException();
            });
}

The response to this request is in JSON format and has the same structure as when the access token was initially granted. This response contains a new refresh token that the client system should store in place of the already used refresh token.


All code for this article can be found at this GitHub link: https://github.com/saetdin/esia/tree/main


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *