Аутентификация в REST/SOAP-сервисах#

Сервисы используют HTTP-аутентификацию двух типов: Basic и Bearer.

Basic — с использованием логина, пароля и имени базы#

При Basic-аутентификации клиент должен передать в запросе:

  • Имя пользователя;

  • Пароль;

  • Имя базы.

Имя пользователя и пароль передаются через HTTP-заголовок Authorization.
Значение: Basic {Base64Cred}, где {Base64Cred} — строка user:password, закодированная в Base64.

Имя базы может быть передано (в порядке приоритета):

  • Через сегмент строки адреса. Пример: http://server/{DATABASE}/;

  • Через HTTP-заголовок Database со значением {DATABASE};

  • Через HTTP-параметр Database со значением {DATABASE}. Пример: http://server/?Database={DATABASE}.

Где:
{DATABASE} — имя базы данных.

Если имя базы не передано ни одним из описанных способов, будет произведена попытка аутентификации с использованием имени базы по умолчанию. База по умолчанию определяется из конфигурационного файла global3.config.xml (в порядке приоритета):

  • Значение атрибута <databases defaultDb="{DATABASE}"/>;

  • Значение атрибута <database alias="{DATABASE}"/> первой базы в списке конфигураций <databases/>.

Bearer — с использованием токена аутентификации#

При Bearer-аутентификации клиент должен передать в запросе:

  • токен аутентификации;

  • имя базы.

До версии AS 1.20 RC 15 включительно, доступны только токены, сформированные сервером приложений на основе: имени базы, имени пользователя и пароля.
До версии AS 1.20 RC 15 включительно, указания имени базы при Bearer-аутентификации не требуется.

Запрос должен содержать HTTP-заголовок Authorization.
Значение Bearer {Token}, где {Token} — ключ авторизации, присвоенный пользователю можно получить в прикладном коде от сущности:

session.user.token: String

Имя базы может быть передано (в порядке приоритета):

  • Через сегмент строки адреса. Пример: http://server/{DATABASE}/;

  • Через HTTP-заголовок Database со значением {DATABASE};

  • Через HTTP-параметр Database со значением {DATABASE}. Пример: http://server/?Database={DATABASE}.

Где:
{DATABASE} — имя базы данных.

Если имя базы не передано ни одним из описанных способов, будет произведена попытка аутентификации с использованием имени базы по умолчанию. База по умолчанию определяется из конфигурационного файла global3.config.xml (в порядке приоритета):

  • Значение атрибута <databases defaultDb="{DATABASE}"/>;

  • Значение атрибута <database alias="{DATABASE}"/> первой базы в списке конфигураций <databases/>.

Типы токенов аутентификации#

Application Server Token#

Токен формируется сервером приложений после входа в приложение Global с использованием имени пользователя и пароля. Токен возвращается клиенту в cookie access_token, привязанной к подмножеству адресов http[s]://server/{DATABASE}/. Эта cookie автоматически присоединяется ко всем запросам по адресам http[s]://server/{DATABASE}/, выполняемым из браузера, аутентифицированного в системе Global.

Токен устаревает после перезапуска сервера приложений.

Gtk Json Web Token#

Токен по стандарту JWT формируется в модуле GTK (или на внешнем сервисе) и валидируется в модуле GTK.

GJWT разделяются на подтипы:

Долгоживущий токен пользователя#

Формируется администратором системы Global и сообщается пользователю любым удобным способом, исключающим утечку токена.
Токен, сопоставленный с учётной записью пользователя, хранится в БД решения.
Срок годности определяется администратором.

Тело JWT (payload) должно содержать следующие поля:

  • typ"UserHash".

  • sub — имя пользователя, под которым необходимо аутентифицироваться.

  • exp — дата устаревания.

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

Подписанный пользователем токен#

Формируется пользователем и подписывается закрытым RSA-ключом пользователя.
Открытый RSA-ключ, сопоставленный с учётной записью пользователя, хранится в БД решения.
Срок годности определяется пользователем.

Тело JWT (payload) должно содержать следующие поля:

  • typ"UserCrt".

  • sub — имя пользователя, под которым необходимо аутентифицироваться.

  • cid — идентификатор открытого ключа в системе Global, сопоставленного с пользователем.

  • exp — дата устаревания.

При проверке валидности токена на основе полей sub и cid из БД решения получается открытый RSA-ключ и проверяется валидность подписи JWT.

Подписанный прокси-пользователем токен#

Прокси-аутентификация — это аутентификация под именем пользователя №2 от имени пользователя №1, именуемого прокси-пользователем.

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

Формируется прокси-пользователем и подписывается закрытым RSA-ключом прокси-пользователя.
Открытый RSA-ключ, сопоставленный с учётной записью прокси-пользователя, хранится в БД решения.
Срок годности определяется прокси-пользователем.

Тело JWT (payload) должно содержать следующие поля:

  • typ"ProxyCrt".

  • sub — имя пользователя, под которым необходимо аутентифицироваться.

  • psub — имя прокси-пользователя, которому принадлежат закрытый и открытый ключи.

  • cid — идентификатор открытого ключа в системе Global, сопоставленного с прокси-пользователем.

  • exp — дата устаревания.

При проверке валидности токена на основе полей psub и cid из БД решения получается открытый RSA-ключ прокси-пользователя и проверяется валидность подписи JWT.
Если токен валиден и пользователь psub имеет права на выполнение кода от имени других пользователей, запрос аутентифицируется под именем пользователя sub.

Пример запроса с аутентификацией по токену#

В примере используются библиотеки:

  • "io.jsonwebtoken" % "jjwt-api" % "0.10.8".

  • "io.jsonwebtoken" % "jjwt-impl" % "0.10.8".

  • "io.jsonwebtoken" % "jjwt-jackson" % "0.10.8".

  • "org.apache.httpcomponents" % "httpclient" % "4.5.8".

import io.jsonwebtoken.Jwts;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.Assert;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Calendar;
import java.util.Date;
import java.util.stream.Collectors;

public class AuthProviderTest {

    private PrivateKey getPrivateKey(String proxyUser) throws NoSuchAlgorithmException {
        // Формируется рандомный закрытый ключ.
        // В рабочем коде необходимо использовать реальный закрытый ключ, принадлежащий пользователю.
        return KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate();
    }

    @Test
    public void authenticate_with_gjwt_proxy_cert() throws Exception {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, 7);
        String proxyUser = "proxy_user";
        String gjwt = "gjwt_" + Jwts.builder()
                .setAudience("GS")                  // Кому предназначен токен. Необязательный параметр.
                .setIssuer("Scheduler")             // Кем сформирован токен. Необязательный параметр.
                .setSubject("real_user")            // Имя пользователя, под которым необходимо аутентифицироваться.
                .claim("typ", "ProxyCrt")           // Подтип токена. В данном случае, токен для прокси-аутентификации.
                .claim("psub", proxyUser)           // Имя прокси-пользователя.
                .claim("cid", "123456789")          // Идентификатор открытого RSA-ключа прокси-пользователя в системе Глобал.
                .setExpiration(calendar.getTime())  // Дата устаревания токена.
                .signWith(getPrivateKey(proxyUser)) // Закрытый ключ прокси-пользователя, которым будет подписан токен.
                .compact();

        HttpGet httpGet = new HttpGet("http://localhost:8080/PGTEST/app/sys/rest/ss/pkg/Gs3_RestfulTestPkg/anypath");
        httpGet.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + gjwt);
        try (CloseableHttpClient client = HttpClients.createDefault()) {
            try (CloseableHttpResponse response = client.execute(httpGet)) {
                try (InputStream in = response.getEntity().getContent()) {
                    String s = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
                            .lines()
                            .collect(Collectors.joining(System.lineSeparator()));
                    System.out.println(s);
                }
            }
        }
    }
}

Администрирование REST-сервисов#

Новое расположение документации

Администрирование SOAP-вызовов#

Новое расположение документации