Аутентификация в 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/>.
Типы токенов аутентификации#
ast— Application Server Token.gjwt— Gtk Json Web Token.
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 разделяются на подтипы:
UserHash— Долгоживущий токен пользователя.UserCrt— Подписанный пользователем токен.ProxyCrt— Подписанный прокси-пользователем токен.
Долгоживущий токен пользователя#
Формируется администратором системы 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);
}
}
}
}
}