- Создание сервиса авторизации через систему ЕСИА
- Что такое ЕСИА?
- 1. Создание Java-проекта
- 2. Реализация сервиса авторизации
- Общие принципы
- Метод получения авторизационного кода по определенному URL
- Генерируем client_secret
- Что же может получить коммерческая организация из ЕСИА?
- Как подключиться?
- Аутентификация пользователя в системе
- Подключаться или нет?
Создание сервиса авторизации через систему ЕСИА
Всем привет. Меня зовут Динис, я старший разработчик в БФТ-Холдинге
.
В данной статье приведен план по созданию сервиса авторизации через систему ЕСИА. Тема будет интересна тем, кто внедряет такой сервис для своего приложения. В статье я собрал ключевые выдержки из документации с частями кода, а весь код вы можете найти в конце статьи по ссылке на 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 += "×tamp=" + 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
.
Что же может получить коммерческая организация из ЕСИА?
Перечень доступных к получению сведений зависит от:
- Категории организации, подключающейся к ЕСИА
- Использованного способа подключения к ЕСИА
Минкомсвязь ограничивает перечень данных, доступных коммерческим организациям. Обычно разрешают получать только сведения о ФИО, реквизитах паспорта (серия и номер, кем и когда выдан), гражданстве, а также признака «подтвержденности» аккаунта и идентификатора аккаунта в ЕСИА.
Государственные организации могут получать из ЕСИА полный набор данных о пользователе и его организациях. Это следующие сведения:
- личные данные (ФИО, пол, дата и место рождения, гражданство)
- данные идентификационных документов (СНИЛС, ИНН, общегражданский и заграничный паспорт, свидетельство о рождении, водительское удостоверение, военный билет, полис ОМС)
- контактная информация (email, мобильный и домашний телефон, адреса регистрации и проживания)
- сведения о детях (личные данные и документы)
- сведения о транспортных средствах (номер и свидетельство о регистрации)
- сведения об организациях и ИП (наименование, ОГРН, ИНН/КПП, организационно-правовая форма, юридический адрес, контакты, филиалы, списки сотрудников, полномочия сотрудников, транспортные средства организации)
- данные учетной записи (идентификатор аккаунта в ЕСИА, признак «подтвержденности» аккаунта)
Сведения предоставляются в том объеме, в каком они заполнены пользователем в ЕСИА, а также при условии согласия пользователя на предоставления этих сведений.
Как подключиться?
Чтобы подключить сайт своей организации, нужно пройти несколько довольно несложных процедур.
В общих чертах для подключения к ЕСИА нужно:
- Убедиться, что вашей организации можно подключать свои системы к ЕСИА.
- Директору организации с помощью веб-приложения « Профиль ЕСИА
» зарегистрировать организацию в ЕСИА.
- Ему также нужно прикрепить к учетной записи организации ответственного сотрудника и назначить ему право доступа в специальное приложение — Технологический портал ЕСИА. Если директор не планирует делегировать дальнейшие операции своему сотруднику, то тогда он все равно должен явно предоставить доступ себе к Технологическому порталу ЕСИА.
Назначенному ответственному сотруднику организации нужно с помощью веб-приложения « Технологический портал ЕСИА
»: - Зарегистрировать учетную запись системы в ЕСИА. Мнемонику для системы придумать, либо использовать существующую мнемонику точки подключения к СМЭВ, если подключаемая к ЕСИА система раньше уже была подключена к СМЭВ.
- Загрузить в карточку системы ее сертификат.
Ответственному сотруднику организации нужно:
- Поочередно подать по электронной почте заполненные в соответствии с регламентом
заявки на использование программных интерфейсов ЕСИА в тестовой и промышленной среде.
Разработчикам подключаемой системы:
- Доработать систему для подключения к ЕСИА, самостоятельно разработав код взаимодействия с ЕСИА в соответствии с действующим документом « Методические рекомендации по использованию ЕСИА
» или использовать готовые решения, благо такие на рынке есть. - Отладить взаимодействие в тестовой и промышленной среде ЕСИА.
Здесь нужно заметить, что с 01.01.2018 г. взаимодействие по протоколу SAML 2.0 больше не будет разрешено
(только для действующих систем). Для подключения к ЕСИА необходимо будет использовать протокол OAuth 2.0 / OpenID Connect (сейчас доступны оба варианта).
Аутентификация пользователя в системе
Рекомендуемый сценарий аутентификации пользователя при интеграции по OpenID Connect 1.0 в его базовом виде происходит по следующему сценарию:
- Пользователь нажимает на веб-странице системы-клиента кнопку «Войти через ЕСИА».
- Система-клиент формирует и отправляет в ЕСИА запрос на аутентификацию и перенаправляет браузер пользователя на специальную страницу предоставления доступа.
- ЕСИА осуществляет аутентификацию пользователя одним из доступных способов. Если пользователь ещё не зарегистрирован в ЕСИА, то он может перейти к процессу регистрации.
- Когда пользователь аутентифицирован, ЕСИА сообщает пользователю, что система-клиент запрашивает данные о нем в целях проведения идентификации и аутентификации, предоставляя перечень запрашиваемых системой-клиентом сведений.
- Если пользователь дает разрешение на проведение аутентификации системой-клиентом, то ЕСИА выдает системе-клиенту специальный авторизационный код.
- Система-клиент формирует в адрес ЕСИА запрос на получение маркера идентификации, включая в запрос полученный ранее авторизационный код.
- ЕСИА проверяет корректность запроса (например, что система-клиент зарегистрирована в ЕСИА) и авторизационного кода и передает системе-клиенту маркер идентификации.
- Система-клиент извлекает идентификатор пользователя из маркера идентификации. Если идентификатор получен, а маркер проверен, то система-клиент считает пользователя аутентифицированным. После получения маркера идентификации система-клиент использует REST-сервисы ЕСИА для получения дополнительных данных о пользователе, предварительно получив соответствующий маркер доступа.
Подключаться или нет?
За операторов, в связи со вступлением в действие закона о мессенджерах данный вопрос практически решен.
Напомним, в соответствии с Федеральным законом №245 «О внесении изменений в Федеральный закон «О связи» от 29 июля 2017 года, операторы связи обязаны проверять достоверность сведений об абоненте. В законе закреплен перечень способов проверки
, одним из которых является использование Единого портала государственных и муниципальных услуг
или информационных систем госорганов при наличии подключения к ним у операторов через СМЭВ.
Поправки в ФЗ «О связи» вступают в силу 1 июня 2018. До этого времени операторы связи смогут протестировать работу своих систем со СМЭВ и ЕСИА.
Становится ли чебурнет всё ближе? Официальных заявлений о планах сделать выход в Интернет возможным только через ЕСИА нами не найдено. На данный момент, по официальным данным, в ЕСИА зарегистрировано около 50 миллионов пользователей (физических лиц) и около 300 000 организаций.