АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА pos gosuslugi

Создание сервиса авторизации через систему ЕСИА

Время на прочтение

Всем привет. Меня зовут Динис, я старший разработчик в БФТ-Холдинге
.

В данной статье приведен план по созданию сервиса авторизации через систему ЕСИА. Тема будет интересна тем, кто внедряет такой сервис для своего приложения. В статье я собрал ключевые выдержки из документации с частями кода, а весь код вы можете найти в конце статьи по ссылке на GitHub.

Что такое ЕСИА?


У сервиса ЕСИА прекрасная документация, поэтому большая часть данной статьи содержит ее выдержки – они выделены курсивом. Полная версия документации доступна по ссылке:
https://digital.gov.ru/ru/documents/6182/


Теперь – к самому интересному.

1. Создание Java-проекта

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

Для начала добавим эти зависимости:

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

В качестве криптопровайдера будем использовать российский криптопровайдер Vipnet, сертифицированный ФСБ России как средство криптографической защиты информации и электронной подписи.

Скачаем с сайта ( https://infotecs.ru/products/vipnet-jcrypto-sdk/
) библиотеки крипторовайдера. Добавим их в директорию jcr/lib в корень проекта.

Добавим зависимости 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()
}
  
  

Заполним наш 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: CLIENT-ID
    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
  
  

Настроим конфигурацию веб-клиента:

   @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();
    }
}
  
  

Так как мы будем работать с jwt токенами, создадим класс JwtUtil:

   @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;
        }
        return token;
    }

    public String withBearerToken(String token) {
        if (!token.startsWith("Bearer ")) {
            return String.format("Bearer %s", token);
        }
        return token;
    }
}
  
  

2. Реализация сервиса авторизации

Общие принципы

Данная модель контроля доступа используется в случаях, когда система-клиент при доступе к ресурсу должна получить разрешение на это действие со стороны владельца ресурса.

В общем виде схема взаимодействия выглядит следующим образом:     

  • Система-клиент запрашивает у владельца ресурса разрешение на доступ к соответствующим ресурсам. Обычно этот запрос осуществляется не напрямую к владельцу ресурса, а через сервис авторизации (который, в свою очередь, запрашивает разрешение у владельца ресурса), поскольку сам владелец ресурса не может выдать ни маркер доступа, ни авторизационный код.

  • Система-клиент получает разрешение на доступ (authorization grant) в виде авторизационного кода.

  • Система-клиент запрашивает маркер доступа, предъявив авторизационный код сервису авторизации.

  • Сервис авторизации аутентифицирует систему-клиента, проверяет авторизационный код и выдает маркер доступа и маркер обновления.

  • Система-клиент запрашивает у поставщика защищенный ресурс, предъявляя маркер доступа.

  • Поставщик ресурса проверяет маркер доступа. Если он валиден, то разрешает доступ к защищенному ресурсу.

  • Система-клиент вновь запрашивает с помощью выданного ранее маркера доступ к защищенному ресурсу.

  • Поставщик ресурса проверяет маркер, обнаруживает, что срок его действия истек, возвращает сообщение об ошибке.

  • Система-клиент обращается к сервису авторизации за получением нового маркера доступа, предъявляя маркер обновления.

  • Сервис авторизации проверяет валидность маркера обновления и возвращает два новых маркера: доступы и обновления.

После того, как система-клиент получила маркер доступа, она может неоднократно обращаться за получением защищенного ресурса до конца срока действия этого маркера. Когда это произойдет, системе-клиенту потребуется получить новый маркер доступа.

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

Метод получения авторизационного кода по определенному URL

Чтобы получить авторизационный код, система-клиент должна получить разрешение на доступ к защищенному ресурсу со стороны его владельца. Если владельцем является пользователь ЕСИА, система-клиент должна направить пользователя на страницу предоставления прав доступа в ЕСИА (пользователь должен быть предварительно аутентифицирован в системе, или она попросит его пройти идентификацию и аутентификацию).

   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;
}
  
  

Эта ссылка содержит:

  • <client_id> – идентификатор системы-клиента (мнемоника системы в ЕСИА, указанная прописными буквами);

  • <client_secret> – подпись запроса в формате PKCS#7 detached signature

    в кодировке UTF-8 от значений четырех параметров HTTP-запроса: scope, timestamp, clientId, state (без разделителей). <client_secret> должен быть закодирован в формате base64 url safe. Используемый для проверки подписи сертификат должен быть предварительно зарегистрирован в ЕСИА и привязан к УЗ системы-клиента в ЕСИА. Е СИА использует сертификаты в формате X.509 и взаимодействует с алгоритмами формирования электронной подписи ГОСТ Р 34.10-2012 и криптографического хэширования ГОСТ Р 34.11-2012;

  • <redirect_uri> – ссылка, по которой должен быть направлен пользователь

    после того, как даст разрешение на доступ к ресурсу;

  • <scope> – область доступа, т.е. запрашиваемые права; например, если система-клиент запрашивает доступ к сведениям о сотрудниках организации,

    то область доступа (scope) должна иметь значение http://esia.gosuslugi.ru/org_emps (с необходимыми параметрами);

    если запрашивается область доступа (scope) id_doc (данные о пользователе), то не нужно в качестве параметра указывать oid этого пользователя;

  • <response_type> – это тип ответа, который ожидается от ЕСИА, имеет значение code, если система-клиент должна получить авторизационный код;

  • <state> – набор случайных символов, имеющий вид 128-битного идентификатора запроса (необходимо для защиты от перехвата), генерируется по стандарту UUID;

  • <timestamp> – время запроса авторизационного кода в формате yyyy. MM.dd HH:mm:ss Z (например, 2013.01.25 14:36:11 +0400), необходимое для фиксации начала временного промежутка, в течение которого будет валиден запрос

    с данным идентификатором (<state>);

  • <access_type> – принимает значение «offline», если требуется иметь доступ

    к ресурсам и тогда, когда владелец не может быть вызван (в этом случае выпускается маркер обновления); значение «online» – доступ требуется только при наличии владельца.

Когда авторизационный код получен, система-клиент может сформировать запрос методом POST на https-адрес ЕСИА для получения маркера доступа. Один авторизационный код можно обменять на один маркер доступа.

   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();
            });
}
  
  

В тело запроса должны быть включены следующие сведения:

  • <client_id> – идентификатор системы-клиента (мнемоника системы в ЕСИА, указанная прописными буквами);

  • <code> – значение авторизационного кода, который был ранее получен

    от ЕСИА, его необходимо обменять на маркер доступа;

  • <grant_type> – принимает значение «authorization_code»,

    если авторизационный код обменивается на маркер доступа;

  • <client_secret> – подпись запроса в формате PKCS#7 detached signature

    в кодировке UTF-8 от значений четырех параметров HTTP-запроса: scope, timestamp, clientId, state (без разделителей). <client_secret> должен быть закодирован в формате base64 url safe. Используемый для проверки подписи сертификат должен быть предварительно зарегистрирован в ЕСИА и привязан к УЗ системы-клиента в ЕСИА. Е СИА использует сертификаты в формате X.509 и взаимодействует с алгоритмами формирования электронной подписи ГОСТ Р 34.10-2012

    и криптографического хэширования ГОСТ Р 34.11-2012;

  • <state> – набор случайных символов, имеющий вид 128-битного идентификатора запроса (необходимо для защиты от перехвата), генерируется по стандарту UUID; этот набор символов должен отличаться от того, который использовался при получении авторизационного кода;

  • <redirect_uri> – ссылка, по которой должен быть направлен пользователь

    после того, как даст разрешение на доступ (то же самое значение, которое было указано в запросе на получение авторизационного кода);

  • <scope> – область доступа, т.е. запрашиваемые права (то же самое значение, которое было указано в запросе на получение авторизационного кода);

  • <scope_org> – область доступа, т.е. запрашиваемые права для юридических лиц (то же самое значение, которое было указано в запросе на получение авторизационного кода);

  • <timestamp> – время запроса маркера в формате yyyy. MM.dd HH:mm:ss Z (например, 2013.01.25 14:36:11 +0400), необходимое для фиксации начала временного промежутка, в течение которого будет валиден запрос с данным идентификатором (<state>);

  • <token_type> – тип запрашиваемого маркера, в настоящее время ЕСИА поддерживает только значение «Bearer». Параметр необязательный.

    Если запрос успешно прошел проверку, то ЕСИА возвращает ответ в формате JSON:

  • <access_token> – маркер доступа для данного ресурса;

  • <expires_in> – время, в течение которого истекает срок действия маркера

    (в секундах);

  • <state> – набор случайных символов, имеющий вид 128-битного идентификатора запроса, генерируется по стандарту UUID (совпадает

    с идентификатором запроса);

  • <token_type> – тип предоставленного маркера, в настоящее время ЕСИА поддерживает только значение «Bearer»;

    <refresh_token> – маркер обновления для данного ресурса.

Генерируем 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);
    }
}
  
  

Если в ходе авторизации не возникло ошибок, то ЕСИА осуществляет перенаправление пользователя по ссылке, указанной в redirect_uri, а также возвращает два обязательных параметра:

  • <code> – значение авторизационного кода;

  • <state> – значение параметра state, который был получен в запросе

    на авторизацию; система-клиент должна провести сравнение отправленного

    и полученного параметра state.

В случае ошибки сервис авторизации вернет в параметре error код ошибки (например, «access_denied») и перенаправит пользователя по адресу, указанному

в redirect_uri.

Когда авторизационный код получен, система-клиент может сформировать запрос методом POST на https-адрес ЕСИА для получения маркера доступа. Один авторизационный код можно обменять на один маркер доступа. В тело запроса должны быть включены следующие сведения:

  • <client_id> – идентификатор системы-клиента (мнемоника системы в ЕСИА указанная прописными буквами);

  • <code> – значение авторизационного кода, который был ранее получен

    от ЕСИА и который необходимо обменять на маркер доступа;

  • <grant_type> – принимает значение «authorization_code»,

    если авторизационный код обменивается на маркер доступа;

  • <client_secret> – подпись запроса в формате PKCS#7 detached signature

    в кодировке UTF-8 от значений четырех параметров HTTP-запроса: scope, timestamp, clientId, state (без разделителей). <client_secret> должен быть закодирован в формате base64 url safe. Используемый для проверки подписи сертификат должен быть предварительно зарегистрирован в ЕСИА и привязан к УЗ системы-клиента в ЕСИА. Е СИА использует сертификаты в формате X.509 и взаимодействует с алгоритмами формирования электронной подписи ГОСТ Р 34.10-2012

    и криптографического хэширования ГОСТ Р 34.11-2012;

  • <state> – набор случайных символов, имеющий вид 128-битного идентификатора запроса (необходимо для защиты от перехвата), генерируется по стандарту UUID; этот набор символов должен отличаться от того, который использовался при получении авторизационного кода;

  • <redirect_uri> – ссылка, по которой должен быть направлен пользователь

    после того, как даст разрешение на доступ (то же самое значение, которое было указано в запросе на получение авторизационного кода);

  • <scope> – область доступа, т.е. запрашиваемые права (то же самое значение, которое было указано в запросе на получение авторизационного кода);

  • <scope_org> – область доступа, т.е. запрашиваемые права для юридических лиц (то же самое значение, которое было указано в запросе на получение авторизационного кода);

  • <timestamp> – время запроса маркера в формате yyyy. MM.dd HH:mm:ss Z (например, 2013.01.25 14:36:11 +0400), необходимое для фиксации начала временного промежутка, в течение которого будет валиден запрос с данным идентификатором (<state>);

  • <token_type> – тип запрашиваемого маркера, в настоящее время ЕСИА поддерживает только значение «Bearer». Параметр необязательный.

   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();
            });
}
  
  

Если запрос успешно прошел проверку, то ЕСИА возвращает ответ в формате JSON:

  • <access_token> – маркер доступа для данного ресурса;

  • <expires_in> – время, в течение которого истекает срок действия маркера

    (в секундах);

  • <state> – набор случайных символов, имеющий вид 128-битного идентификатора запроса, генерируется по стандарту UUID (совпадает

    с идентификатором запроса);

  • <token_type> – тип предоставленного маркера, в настоящее время ЕСИА поддерживает только значение «Bearer»;

  • <refresh_token> – маркер обновления для данного ресурса.

При использовании маркера доступа системам-клиентам рекомендуется сначала проверять, не истек ли срок его действия. Если маркер просрочен, то для успешного доступа к защищенному ресурсу потребуется предварительно получить новый маркер доступа с использованием маркера обновления. Для этого системе-клиенту следует сформировать запрос методом POST в адрес ЕСИА, имеющий структуру, аналогичную первичному запросу на получение маркера. Особенности значений параметров запроса:

  • <refresh_token> – значение имеющегося у системы-клиента маркера обновления, который следует обменять на новый маркер доступа (указывается вместо <code>)

  • <grant_type> – должно иметь значение «refresh_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();
            });
}
  
  

Ответ на этот запрос дается в формате JSON и имеет ту же структуру, как и при первичном предоставлении маркера доступа. В этом ответе содержится новый маркер обновления, который система-клиент должна хранить вместо уже использованного маркера обновления.


Весь код данной статьи можно найти по ссылке на GitHub:
https://github.com/saetdin/esia/tree/main


Время на прочтение

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

Изменения в законодательстве, начинающие действовать с начала 2018 года и включающие в себя самые разнообразные области нашей с вами жизнедеятельности (закон о мессенджерах, о телемедицине и т.д.) объединяет одно – все большее проникновение информационных сервисов в нашу жизнь. Естественным является факт, что как и в реальной жизни, для получения человеком какой-либо услуги ему требуется пройти идентификацию. В офф-лайновой жизни средством авторизации является паспорт гражданина, а в он-лайн сфере таковым средством правительство решило признать ЕСИА — единая система идентификации и аутентификации.

Вот о ней и хотелось бы поговорить. Это ознакомительная статья, можно сказать ликбез. Для знакомства людей, которые еще не знают, что при необходимости можно использовать ЕСИА у себя в проектах и идти в ногу со временем вместе с государством. И так, что же это за зверь и как его рассматривает правительство.

Минкомсвязь России в рамках инфраструктуры электронного правительства создала и развивает Единую систему идентификации и аутентификации (ФГИС ЕСИА), цель которой — упорядочить и централизовать процессы регистрации, идентификации, аутентификации и авторизации пользователей.

1. Предоставляет информационным системам решение по достоверной идентификации пользователей (физических и юридических лиц, органов государственной власти).

Достоверность достигается за счет того, что:

  • регистрация лица в ЕСИА сопряжена с проверкой значимых для удостоверения личности критериев;
  • ЕСИА обеспечивает защиту размещённой в ней информации в соответствии с законодательством Российской Федерации.

2. Является ориентированной на пользователя и предоставляет возможности:

  • идентификации и аутентификации с использованием единой учетной записи и широкого спектра поддерживаемых методов аутентификации при доступе к различным информационным системам органов государственной власти;
  • управления своими персональными данными, размещенными в ЕСИА, и контроля над их предоставлением в информационные системы органов государственной власти.

Основные функциональные возможности ЕСИА:

  • идентификация и аутентификация пользователей, в том числе:
    • однократная аутентификация, которая дает пользователям ЕСИА следующее преимущество: пройдя процедуру идентификации и аутентификации в ЕСИА, пользователь может в течение одного сеанса работы обращаться к любым информационным системам, использующим ЕСИА, при этом повторная идентификация и аутентификация не требуется;
    • поддержка различных методов аутентификации: по паролю, по электронной подписи, а также двухфакторная аутентификация (по постоянному паролю и одноразовому паролю, высылаемому в виде sms-сообщения);
    • поддержка уровней достоверности идентификации пользователя (упрощённая учетная запись, стандартная учетная запись, подтвержденная учетная запись).
  • ведение идентификационных данных, а именно – ведение регистров физических, юридических лиц, органов и организаций, должностных лиц органов и организаций и информационных систем;
  • авторизация уполномоченных лиц органов государственной власти при доступе к следующим функциям ЕСИА:
    • ведение регистра должностных лиц органов власти в ЕСИА;
    • ведение справочника полномочий в отношении информационной системы и предоставление пользователям ЕСИА (зарегистрированным в ЕСИА как должностные лица) полномочий по доступу к ресурсам систем, зарегистрированным в ЕСИА;
    • делегирование вышеуказанных полномочий уполномоченным лицам нижестоящих органов государственной власти.
  • ведение и предоставление информации о полномочиях пользователей в отношении информационных систем, зарегистрированных в ЕСИА.

В данный момент подключить систему к ЕСИА может любая государственная организация, а также отдельные виды коммерческих организаций: страховые компании, кредитные организации (банки), профессиональные участники рынка ценных бумаг, негосударственные пенсионные фонды, микрофинансовые и микрокредитные организации, а также операторы связи.

Законодательство со временем корректируется, и вместе с ним расширяется перечень организаций, которым разрешено подключиться к ЕСИА.

Люди, не знакомые с ситуацией, при слове «государственная» сразу представляют себе каналы связи, которые необходимо защитить при помощи отечественных криптоалгоритмов со всеми вытекающими затратами, лицензиями и оборудованием. Но, как бы смешно это не было (или печально), главная площадка для идентификации в стране – работает с иностранной криптографией (а куда деваться).

Поэтому, если вы хотите воспользоваться услугами данной платформы, то можете размещать свои ресурсы где у годно в нашей огромной стране, в том числе и в нашей инфраструктуре Cloud4Y
.

Что же может получить коммерческая организация из ЕСИА?

Перечень доступных к получению сведений зависит от:

  1. Категории организации, подключающейся к ЕСИА
  2. Использованного способа подключения к ЕСИА

Минкомсвязь ограничивает перечень данных, доступных коммерческим организациям. Обычно разрешают получать только сведения о ФИО, реквизитах паспорта (серия и номер, кем и когда выдан), гражданстве, а также признака «подтвержденности» аккаунта и идентификатора аккаунта в ЕСИА.

Государственные организации могут получать из ЕСИА полный набор данных о пользователе и его организациях. Это следующие сведения:

  1. личные данные (ФИО, пол, дата и место рождения, гражданство)
  2. данные идентификационных документов (СНИЛС, ИНН, общегражданский и заграничный паспорт, свидетельство о рождении, водительское удостоверение, военный билет, полис ОМС)
  3. контактная информация (email, мобильный и домашний телефон, адреса регистрации и проживания)
  4. сведения о детях (личные данные и документы)
  5. сведения о транспортных средствах (номер и свидетельство о регистрации)
  6. сведения об организациях и ИП (наименование, ОГРН, ИНН/КПП, организационно-правовая форма, юридический адрес, контакты, филиалы, списки сотрудников, полномочия сотрудников, транспортные средства организации)
  7. данные учетной записи (идентификатор аккаунта в ЕСИА, признак «подтвержденности» аккаунта)

Сведения предоставляются в том объеме, в каком они заполнены пользователем в ЕСИА, а также при условии согласия пользователя на предоставления этих сведений.

Как подключиться?

Чтобы подключить сайт своей организации, нужно пройти несколько довольно несложных процедур.

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

В общих чертах для подключения к ЕСИА нужно:

  1. Убедиться, что вашей организации можно подключать свои системы к ЕСИА.
  2. Директору организации с помощью веб-приложения « Профиль ЕСИА
    » зарегистрировать организацию в ЕСИА.

    АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

    АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

  3. Ему также нужно прикрепить к учетной записи организации ответственного сотрудника и назначить ему право доступа в специальное приложение — Технологический портал ЕСИА. Если директор не планирует делегировать дальнейшие операции своему сотруднику, то тогда он все равно должен явно предоставить доступ себе к Технологическому порталу ЕСИА.

    Назначенному ответственному сотруднику организации нужно с помощью веб-приложения « Технологический портал ЕСИА
    »:

  4. Зарегистрировать учетную запись системы в ЕСИА. Мнемонику для системы придумать, либо использовать существующую мнемонику точки подключения к СМЭВ, если подключаемая к ЕСИА система раньше уже была подключена к СМЭВ.
  5. Загрузить в карточку системы ее сертификат.

    Ответственному сотруднику организации нужно:

  6. Поочередно подать по электронной почте заполненные в соответствии с регламентом
    заявки на использование программных интерфейсов ЕСИА в тестовой и промышленной среде.

    АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

    Разработчикам подключаемой системы:

  7. Доработать систему для подключения к ЕСИА, самостоятельно разработав код взаимодействия с ЕСИА в соответствии с действующим документом « Методические рекомендации по использованию ЕСИА
    » или использовать готовые решения, благо такие на рынке есть.
  8. Отладить взаимодействие в тестовой и промышленной среде ЕСИА.

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

Здесь нужно заметить, что с 01.01.2018 г. взаимодействие по протоколу SAML 2.0 больше не будет разрешено
(только для действующих систем). Для подключения к ЕСИА необходимо будет использовать протокол OAuth 2.0 / OpenID Connect (сейчас доступны оба варианта).

Аутентификация пользователя в системе

Рекомендуемый сценарий аутентификации пользователя при интеграции по OpenID Connect 1.0 в его базовом виде происходит по следующему сценарию:

  1. Пользователь нажимает на веб-странице системы-клиента кнопку «Войти через ЕСИА».
  2. Система-клиент формирует и отправляет в ЕСИА запрос на аутентификацию и перенаправляет браузер пользователя на специальную страницу предоставления доступа.
  3. ЕСИА осуществляет аутентификацию пользователя одним из доступных способов. Если пользователь ещё не зарегистрирован в ЕСИА, то он может перейти к процессу регистрации.
  4. Когда пользователь аутентифицирован, ЕСИА сообщает пользователю, что система-клиент запрашивает данные о нем в целях проведения идентификации и аутентификации, предоставляя перечень запрашиваемых системой-клиентом сведений.
  5. Если пользователь дает разрешение на проведение аутентификации системой-клиентом, то ЕСИА выдает системе-клиенту специальный авторизационный код.
  6. Система-клиент формирует в адрес ЕСИА запрос на получение маркера идентификации, включая в запрос полученный ранее авторизационный код.
  7. ЕСИА проверяет корректность запроса (например, что система-клиент зарегистрирована в ЕСИА) и авторизационного кода и передает системе-клиенту маркер идентификации.
  8. Система-клиент извлекает идентификатор пользователя из маркера идентификации. Если идентификатор получен, а маркер проверен, то система-клиент считает пользователя аутентифицированным. После получения маркера идентификации система-клиент использует REST-сервисы ЕСИА для получения дополнительных данных о пользователе, предварительно получив соответствующий маркер доступа.

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

Подключаться или нет?

За операторов, в связи со вступлением в действие закона о мессенджерах данный вопрос практически решен.

Напомним, в соответствии с Федеральным законом №245 «О внесении изменений в Федеральный закон «О связи» от 29 июля 2017 года, операторы связи обязаны проверять достоверность сведений об абоненте. В законе закреплен перечень способов проверки
, одним из которых является использование Единого портала государственных и муниципальных услуг
или информационных систем госорганов при наличии подключения к ним у операторов через СМЭВ.

Поправки в ФЗ «О связи» вступают в силу 1 июня 2018. До этого времени операторы связи смогут протестировать работу своих систем со СМЭВ и ЕСИА.

АВТОРИЗАЦИЯ ЧЕРЕЗ ЕСИА

Становится ли чебурнет всё ближе? Официальных заявлений о планах сделать выход в Интернет возможным только через ЕСИА нами не найдено. На данный момент, по официальным данным, в ЕСИА зарегистрировано около 50 миллионов пользователей (физических лиц) и около 300 000 организаций.

Читайте также:  Новые пособия к новому году 2022
Оцените статью