Сервисы сервера приложений
Contents
Сервисы сервера приложений#
Сервисы реализованы в опциональном модуле sys, который входит
в дистрибутив Global Server Scala Edition.
Так же модуль может быть подключен в конфигурационном файле сервера
global3.config.xml
:
<sbt name="sys"
lazyLoad="false"
source="C:\global3\sysappbin\"
sourceMode="Jar"
binaryFolder="C:\global3\sysappbin\"
/>
Ленивая загрузка должна быть отключена, для того чтобы сервисы могли всегда принимать запросы.
Более подробную информацию смотрите в руководстве по администрированию сервера.
SSH консоль сервера#
Введение#
Global 3 SE Server включает в себя SSH (Secure Shell) сервер, к которому возможно подключиться с помощью любого SSH-терминала. С помощью команд командной строки возможно:
Смотреть статистику, логи, и управлять клиентскими сессиями
Перезагружать код прикладных приложений
Перезапускать Web-приложения
Выполнять SQL и Jexl-скрипты
Конфигурирование#
Конфигурационный файл global3.config.xml может содержать секцию <ssh/>
:
<ssh defaultDb="PostgreSql" port="22"/>
defaultDb
- Алиас базы данных по умолчанию
Данное значение используется в качестве значения параметра по умолчанию для ssh-командыlogin
. По имени базы будет определены имя и пароль пользователя, для подключения к базе.port
– Порт ssh-сервера
По умолчанию: 22.
Данное значение может быть jvm опцией-Dglobal.ssh.port=xxxx
или параметрами командной строки при запуске Global3 сервера
-global.ssh.port xxxx
Учётные данные пользователей, имеющих права на ssh-сединения, указываются в секции <security/>
конфигурационного файл global3.config.xml:
<security>
<users>
<user name="admin" password="admin" roles="ssh"/>
</users>
</security>
user
– Пользовательpassword
|encryptedPassword
– Пароль
Пароль может быть указан как в явном, так и в зашифрованном виде.roles
– Роли
Список ролей, через запятую, доступных пользователю.
Шифрование пароля#
Для шифрования пароля необходимо запустить Global 3 Server c парамерами
Start.bat -encryptPassword password -masterPassword masterpassword
Где:
-encryptPassword
Пароль который необходимо зашифровать-masterPassword
Ключ шифрования, если не указан используется секретный ключ по умолчанию.
Результатом выполнения будет вывод в консоль строки, полученной в
результате шифрования пароля переданного параметром -encryptPassword
.
Подключение#
Подключиться к SSH-серверу возможно любым SSH-терминалом. Рекомендуемым клиентом для подключения является PuTTY.
Основные параметры#
Для подключения необходимо указать:
Host Name
host или user@hostPort
22 или пользовательскийConnection type
SSH
Что бы не вводить значения полей при повторном запуске PuTTY, можно сохранить параметры подключения по умолчанию, нажав кнопку «Save».
Пример запуска из командной строки:
"C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -pw admin
Логирование#
Для сохранения всего текстового вывода в файл, на вкладке Logging диалога подключения необходимо указать:
Session Logging =
All session output
Log file name =
полный путь к файлу
Внимание
В адресах пути:
Символ
\
необходимо экранировать удвоением
Пример:\\
Символ
/
экранировать не надо
Подключение с использованием SSH-RSA ключа#
При подключении к SSH из
*.bat
иил *.sh
можно воспользоваться RSA-ключами вместо пароля.
"C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -i ssh_admin_private.ppk
Необходимые файлы размещены в подкаталоге .\ssh
.
Ключи для подключения к пользователю admin
выдаются по запросу.
Запуск терминала#
При первом подключении к серверу будет выдано сообщение с запрашивающие разрешение на подключение к серверу, нажмите: Да.
Команды#
Для получения актуальной справки по доступным командам, выполните команду help
.
Список команд:
alter server mode service|normal
Переключает сервер в сервисный режим и обратно. В сервисном режиме возможно подключение пользовательских сессий только от имени системного пользователя сервера приложений, указанного в конфигурационном файле.<systemUsers> <user name="system" password="system"/> </systemUsers>
attach session {sid}
Подключение к существующей пользовательской сессии.attach db {dbAlias} [as sys]
Подключение к базе данных для выполнения сервисных операций и/или Jexl-скриптов.сlear
Очищает экран терминала.clear persistence cache [{dbAlias}]
Очищает Shared-кэш объектов.compare applib {path}
Сравнивает jar-файлы в каталоге (или zip-архиве){path}
с jar-файлами в каталогеSBT.jarFolder
, соответствующему базе данных, к которой выполнено подключение командойattach db
.Выполняется сравнение версий модулей.
Выполняется сравнение *.odm.xml файлов на предмет существования новых атрибутовcopy applib [force] {path}
Выполняет копирование jar-файлов из каталога{path}
в каталогSBT.jarFolder
, соответствующий базе данных, к которой выполнено подключение командойattach db
. При указании ключевого словаforce
перед копированием не выполняется сравнения*.odm.xml
файлов. Сравнение версий модулей выполняется в любом случаи.Init schema
Выполняет создание/обновление объектов схемы БДexecute {expression}
Выполняет однострочное Jexl-выражениеexit
Закрывает SSH-подключениеjexl [{file}]
Переключает терминал в режим ввода и выполнения
Выполнение SQL#
Для выполнения sql-скрипта:
Подключитесь к сессии командами
login
,attach
илиset sid
.Перейдите в режим ввода скрипта командой
sql
.Введите текст скрипта.
Выполните скрипт
Для этого введите символ/
с новой строки.Выйдите из режима ввода скрипта
Для этого введите символ/
с новой строки.
Пример:
login admin/admin@postgres
sql
INSERT INTO gs3_roottest (
id,
idClass
) VALUES (
(select
nextval('GS3_ROOTTEST_SEQ'))
, 12351
) ON CONFLICT DO NOTHING;
/
/
Выполнение SQL скрипта из файла#
Для выполнения sql-скрипта из файла:
Подключитесь к сессии.
Выполните команду
sql {file}
.
Где{file}
– путь к файлу к файлу на сервере.Внимание
Файл должен находится на локальном диске сервера.
Пример:
login admin/admin@postgres
sql D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_sql_exams.txt
Содержимое файла:
INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING;
INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING;
/
INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING;
INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING;
/
/
Jexl скрипты#
Выполнение Jexl скрипта#
Для выполнения Jexl-скрипта необходимо:
Подключитесь к сессии
Переключитесь в режим ввода скрипта командой
jexl
.Введите текст скрипта.
Выполните скрипт
Для этого введите символ/
с новой строки.Выйдите из режима ввода
Для этого введите символ/
с новой строки.
Пример 1:
login admin/admin@postgres
jexl
var name = Btk_ClassApi.getCanonicalClassName("Btk_Object");
Btk_ClassApi.getApiByCanonicalClassName(name);
/
/
Пример 2 (для dataInstall):
login admin/admin@postgres
jexl
Bbb_DBTypeApi.dataInstall();
Btk_Pkg.commit();
/
/
Пример 3:
login admin/admin@postgres
jexl
Btk_Pkg.setRWSharedUOWEditType();
Prs_EntTransApi.dataInstall();
Btk_Pkg.commit();
/
/
Выполнение Jexl скрипта из файла#
Для выполнения Jexl-скрипта из файла необходимо:
Подключитесь к сессии
Выполнить команду jexl
{file}
Где{file}
– путь к файлу к файлу на сервере.Внимание
Файл должен находится на локальном диске сервера.
Пример:
login
jexl D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_jexl_exams.txt
Содержимое файла:
var name = Btk_ClassApi.getCanonicalClassName("Btk_Object");
Btk_ClassApi.getApiByCanonicalClassName(name);
/
/
Контекст выполнения Jexl скрипта#
Возможны следующие контексты выполнения Jexl-скрипта.
Пользовательская сессия#
>login user/password@alias
>jexl
jexl>
/
/
>
В данном контексте доступны все Api-классы, присутствующие в SBT, соответсвующего пользовательской сессии.
База данных#
>attach db alias
>jexl
jexldb>
/
/
>
Доступны методы управления инстансом базы данных:
initschema()
Выполняет инициализацию/обновление схемы БД в соответствии с текущей прикладной кодовой базой.
Системный контекст#
>attach db alias as sys
>jexl
jexlsys>
/
/
>
Данный контекст является административным и предназначен для управления сервером приложений. Доступны следующие методы:
upgrade({release_path})
Выполняет инициализацию/обновление схемы БД в соответствии с текущей прикладной кодовой базой.
Где:
{release_path}
– путь к каталогу или zip-архиву с релизом прикладных модулей
Выполнение командного файла ssh#
Для выполнения командного файла из командной строки можно выполнить:
"C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -pw admin -m shh_logger_test_script.txt
Внимание
В данном примере файл shh_logger_test_script.txt размещён на диске клиентской машины, он считывается в момент подключения к SSH-серверу. Пути к файлам в скрипте, являются локальными для сервера.
Содержимое файла:
login
log-info app el
execute Btk_ClassApi.getCanonicalClassName("Btk_Object")
jexl
var name = Btk_ClassApi.getCanonicalClassName("Btk_Object");
Btk_ClassApi.getApiByCanonicalClassName(name);
/
/
jexl D:/svn/depot/ASSource/sysapplication/ssh/src/test/java/ru/bitec/app/ssh/shh_jexl_exams.txt
jexl D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_jexl_exams.txt
sql
INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING;
/
/
sql D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_sql_exams.txt
log-off all
logout
Логирование#
При выполнении скрипта, по умолчанию, в ssh-консоль выводится результат выполнения команды или сообщение об ошибке со стеком вызова.
Для вывода в ssh-консоль дополнительных логов, необходимо их включить. В консоль можно вывести логи следующих типов:
oper
Логи из классов с namespaceru.bitec.engine.model.operation
.sql
Логи sql-вызовов с уровня jdbc-соединения с базой.script
Логи из скриптового языка в режиме совместимости с Global 1. При работе со Scala не имеют смысла.app
Логи из классов прикладной логики с namespaceru.bitec.app.*
.
Пример отправки сообщения:Logger.Factory.get(Xxx_XxxApi.class).info("Текст сообщения")
el
Логи из инфраструктуры EclipseLink. В основном, это sql-вызовы, в более компактном виде, чем логи jdbc.all
Все выше перечисленные типы логов.
Для переключения уровней логирования используются команды:
log-off
log-error
log-warn
log-info
log-debug
log-trace
Команды указаны в порядке уменьшения уровня логов.
В результате выполнения этой команды:
log-info app el
в ssh-лог будут попадать сообщения типов app
и el
для уровней
логирования: error
, warn
, info
.
Для отключения вывода сообщений в ssh-лог необходимо вызвать команду:
log-off all
Перенаправление ssh-лога Putty в файл#
При выполнении скриптов из командной строки, часто необходимо
перенаправить вывод в файл. Для ssh-консоли Putty, это выполняется
передачей параметра -sessionlog {имя_файла.txt}
"C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -pw admin -m shh_logger_test_script.txt -sessionlog sessionlog.txt
FAQ#
Зависает ssh-терминал на этапе подключения к серверу.#
При запуске Putty из скрипта при первом подключении к серверу выдается сообщение PuTTY Security Alert, что может приводить к зависанию скрипта.
Необходимо дополнительно передать подтверждение при запуске:
echo yes | %plink% -ssh localhost -P 2222 -l admin -i ssh_admin_private.ppk
Примечание
Plink также входит в состав дистрибутива PuTTY.
WebSocket консоль сервера#
Введение#
Global 3 SE Server предоставляет консоль управления, доступную через WebSocket соединение.
С помощью команд передаваемых через WebSocket возможно:
Перезагружать код прикладных приложений
Выполнять Jexl-скрипты
Алгоритм работы с консолью#
Открытие WebSocket-соединения
Отправка команды аутентификации пользователя в системе
Отправка исполняемых команд
Закрытие WebSocket-соединения
Открытие WebSocket соединения#
Для окрытия WebSocket соединения с консолью сервера, необходимо выпонить http-запрос по адресу: ws[s]://{server[:port]}/app/sys/ws/console
.
Формат команды#
Командой является строка в формате:
{command}[\n{arguments}]
где:
{command}
- строка команды. Может состоять из одного или нескольких слов.
\n
- символ новой строки #10. Является разделителем команды и её аргументов. Не обязателен, если у команды нет аргуметтов.
{arguments}
- строка аргументов команды. Может содержать любые символы, включая перносы строк.
Формат результата выполнения команды#
Результатом выполения команды является строка в формате JSON:
{
"success":true,
"data": null,
"exception":null,
"exceptionStack":null
}
где:
success
- Флаг успешности выполнения команды: true|false
data
- Результат выполненения команды: null|"string"|JSON
exception
- Сообщение возникшего исключения: null|"string"
exceptionStack
- Стек возникшего исключения: null|"string"
Список команд#
login
\n
{user}/{password}@{database}
Выполняет аутентификацию пользователя
user
в базе данныхdatabase
и запускает рабочий сеанс пользователя.Использование команды
login
, в качестве способа уатентификации, было выбрано по причине невозможности использования http-заголовков некоторыми WebSocket-клиентами. Например: JMeter WebSocket Load Testing Sampler.logout
Закрывает рабочий сеанс пользователя.
reload sbt
Выполняет перезагрузку прикладного кода текущего решения.
reload sbt force
Выполняет перезагрузку инфраструктуры EclipseLink, прикладного кода и общих библиотек текущего решения.
jexl
\n
// Произвольный текст Jexl-скрипта
return 1 + 2;
Любое значение, возвращённое из Jexl-скрипта, будет преобразовано в строку.
Java пример взаимодествия с консолью#
Библиотеки, используемые в примере клиента:
«org.asynchttpclient» % «async-http-client» % «2.12.3»
«com.fasterxml.jackson.core» % «jackson-databind» % «2.8.9»
package ru.bitec.app.examples.ws.console;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.ws.WebSocket;
import org.asynchttpclient.ws.WebSocketListener;
import org.asynchttpclient.ws.WebSocketUpgradeHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.websocket.CloseReason;
import java.io.Serializable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class WsConsoleClientTest {
public static void main(String[] args) throws Exception {
try (WsConsoleClient client = WsConsoleClient.open("ws://localhost:8080/app/sys/ws/console")) {
client.login("admin", "admin", "pgtest");
System.out.println("1 + 2 = " + client.evaluateJexl("return 1 + 2;"));
client.reloadSbt(false);
client.logout();
}
}
public class WsConsoleClient implements AutoCloseable {
public static WsConsoleClient open(String url) throws Exception {
return open(url, 3000);
}
public static WsConsoleClient open(String url, int timeout) throws Exception {
return new WsConsoleClient().connect(url, timeout);
}
private final ObjectMapper objectMapper_ = new ObjectMapper();
private int commandTimeout_ = 60000;
private AsyncHttpClient asyncHttpClient_;
private WebSocket webSocketClient_;
private volatile CompletableFuture<String> webSocketResultFuture_;
private final WebSocketUpgradeHandler wsHandler = new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() {
@Override
public void onOpen(WebSocket websocket) {
// WebSocket connection opened
}
@Override
public void onClose(WebSocket websocket, int code, String reason) {
if (code != CloseReason.CloseCodes.NORMAL_CLOSURE.getCode()) {
webSocketResultFuture_.completeExceptionally(new Exception(reason));
}
}
@Override
public void onError(Throwable t) {
webSocketResultFuture_.completeExceptionally(t);
}
@Override
public void onTextFrame(String payload, boolean finalFragment, int rsv) {
webSocketResultFuture_.complete(payload);
}
}).build();
public WsConsoleClient connect(String url, int timeout) throws Exception {
AsyncHttpClient syncHttpClient = Dsl.asyncHttpClient();
try {
webSocketClient_ = syncHttpClient
.prepareGet(url)
.setRequestTimeout(timeout)
.execute(wsHandler)
.get();
} catch (Exception e) {
syncHttpClient.close();
throw e;
}
asyncHttpClient_ = syncHttpClient;
return this;
}
public void close() throws Exception {
if (asyncHttpClient_ != null) {
try {
if (webSocketClient_ != null && webSocketClient_.isOpen()) {
try {
webSocketClient_.sendCloseFrame().get();
} finally {
webSocketClient_ = null;
}
}
} finally {
asyncHttpClient_.close();
}
}
}
public int getCommandTimeout() {
return commandTimeout_;
}
public void setCommandTimeout(int commandTimeout) {
this.commandTimeout_ = commandTimeout;
}
public void login(String user, String password, String database) throws Exception {
sendCommand("login", String.format("%s/%s@%s", user, password, database));
}
public void logout() throws Exception {
sendCommand("logout");
}
public String evaluateJexl(String script) throws Exception {
return sendCommand("jexl", script);
}
public void reloadSbt(boolean force) throws Exception {
sendCommand("reload sbt" + (force ? " force" : ""));
}
String sendCommand(String command) throws Exception {
return sendCommand(command, null);
}
String sendCommand(String command, String args) throws Exception {
validateConnection();
if (command == null || command.trim().isEmpty()) {
throw new Exception("Command can not be null or empty.");
}
webSocketResultFuture_ = new CompletableFuture<>();
try {
String payload = command + "\n" + (args != null ? args : "");
webSocketClient_.sendTextFrame(payload).get();
} catch (Exception e) {
webSocketResultFuture_.completeExceptionally(e);
}
return parseResponse(webSocketResultFuture_.get(commandTimeout_, TimeUnit.MILLISECONDS));
}
private String parseResponse(String response) throws Exception {
ConsoleResponse consoleResponse = objectMapper_.readValue(response, ConsoleResponse.class);
if (consoleResponse.success) {
return consoleResponse.data;
} else {
throw new Exception(consoleResponse.exception);
}
}
private void validateConnection() throws Exception {
if (webSocketClient_ == null) {
throw new Exception("WebSocket connection is not connected.");
}
if (!webSocketClient_.isOpen()) {
throw new Exception("WebSocket connection is closed");
}
CompletableFuture<String> webSocketResultFuture = webSocketResultFuture_;
if (webSocketResultFuture != null && !webSocketResultFuture.isDone()) {
throw new Exception("Prior console command is not completed. Wait for the result of the previous command.");
}
}
private final static class ConsoleResponse implements Serializable {
private static final long serialVersionUID = -5577579081118070434L;
/**
* Флаг успешного выполнения запроса. false, если при обработке запроса возникло исключение.
*/
private boolean success = false;
/**
* Строковые данные
*/
private String data;
/**
* Текст возникшего исключения
*/
private String exception;
/**
* Стек возникшего исключения
*/
private String exceptionStack;
public boolean getSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
public String getExceptionStack() {
return exceptionStack;
}
public void setExceptionStack(String exceptionStack) {
this.exceptionStack = exceptionStack;
}
}
}
}
Jexl через SOAP XML#
Введение#
В Global 3 SE Server реализован SOAP XML сервис, позволяющий выполнять Jexl-скрипт в контексте пользовательской сессии.
Аутентификация#
Читайте в разделе Аутентификация в REST/SOAP-сервисах.
Схемы#
WSDL описание сервиса доступно по адресу:
http://{server:port}/app/sys/soap/sys-service-1.0.0?wsdl
XSD схемы POJO запроса и ответа доступны по адресу:
http://{server:port}/app/sys/soap/sys-service-1.0.0?xsd=1
Передача бинарных данных с использованием MTOM#
Сервис поддерживает оптимизированную передачу бинарных данных в сервис и
обратно, через поля attachment объектов JexlSoapRequest
и
JexlSoapResponse
.
Доступ к данным SOAP-запроса из прикладного кода#
В прикладном коде Api-/Pkg-классов, выполняющемся в результате вызова
Jexl-скрипта из SOAP-сервиса, доступен объект SoapContext
. SoapContext
предоставляет доступ к данным SOAP-запроса и SOAP-ответа. Для получения
контекста, выполните:
val soapContextOpt = SoapContext()
Методы доступные в SoapContext
:
hasInputAttachment: Boolean
Указывает на наличие у SOAP-запроса прикреплённых с использованием MTOM бинарных данныхgetInputData: String
Возвращает строковые данные SOAP-запроса.forInputStream(foo: InputStream => Unit): Unit
Предоставляет доступ к прикреплённым бинарным даннымsetOutputData(data: String): Unit
\ Устанавливает строковые данные в SOAP-ответ.forOutputStream(foo: OutputStream => Unit): Unit
Предоставляет доступ к выходному потоку бинарных данных передаваемых средствами MTOM
Методы обработки данных SOAP-запроса из Jexl-скрипта#
В пакете Gtk_SoapPkg
хранятся методы, использующие SoapContext
:
setOutputData(value: AnyRef): Unit
Установливает вывод результата выполнения soap-запроса.inputStreamToTemp: File
Создает временный файл с данными из прикреплённых данных soap-запросаattachFile(file: File): Unit
Принимает файл, закачаивает его данные в выходной поток бинарных данных soap-запросаПример ипользования из Jexl-скрипта:
var f = new("java.io.File", 'C:\users\userName\data.txt');
Gtk_SoapPkg.setOutputData('Данные в прикрепленном файле');
Gtk_SoapPkg.attachFile(f);
Данный скрипт, отправленный через SOAP-запрос, в ответ запишет
в data - строку „Данные в прикрепленном файле“
в attachment - закодированный base64 файл
Jexl через REST API сервис c использованием прикладных пакетов#
Введение#
В Global 3 SE Server реализован REST API сервис, позволяющий выполнять Jexl-скрипт в контексте пользовательской сессии. В данной главе описывается выполенение jexl-скриптов c использованием прикладных пакетов, наследуемых от RestPkg.
Выполнение Jexl в контексте главной выборки приложения описано в отдельной главе.
Аутентификация#
Читайте в разделе Аутентификация в REST/SOAP-сервисах.
Адреса#
Доступно с AS 1.14 RC7:
http://{server:port}/app/sys/rest/es/pkg/Btk_JexlGatePkg/execute
http://{server:port}/app/sys/rest/ss/pkg/Btk_JexlGatePkg/execute
где:
{server:port}
Адрес подключения к серверуapp/sys
Системный прикладной модульrest
Шлюз для rest запросовes
/ss
Группировка по времени жизни gtk-сессии (Exclusive Session) / (Shared Session)pkg
Узел для доступа к пакетам.Btk_JexlGatePkg
Имя прикладного пакета, предназначенного для выполнения jexl-скриптов (подробнее о Rest-пакетах)
Администрирование выполнения jexl-скриптов#
Администрирование выполнения jexl-скриптов осуществляется через право роли на «Функции Jexl». Также можно отдельно ограничить доступ роли к Btk_JexlGatePkg.
Структура запроса#
Тело POST запроса имеет вид
{
"jexl": "jexl-скрипт"
}
Формат результата выполнения команды#
Результатом выполения команды является строка в формате JSON:
{
"exception":null,
"exceptionStack":null,
"data": null,
"success":true,
"exceptionName":null,
}
где:
exception
- Сообщение возникшего исключения: null|"string"
exceptionStack
- Стек возникшего исключения: null|"string"
success
- Флаг успешности выполнения команды: true|false
data
- Результат выполненения команды: null|"string"|JSON|Boolean
exceptionName
- Имя класса исключения: null|"string"
Пример использования#
Для выполнения jexl-скрипта будем использовать пакет Btk_JexlGatePkg и Shared Session, тогда адрес запроса будет выглядеть так:
http://localhost:8080/app/sys/rest/ss/pkg/Btk_JexlGatePkg/execute
В качестве примера Jexl-скрипт будет выполнять проверку, является ли пользователь супер-пользователем:
{
"jexl": "Btk_UserApi.isSuperUser()"
}
После выполнения POST-запроса сервер вернёт ответ:
{
"exception": null,
"exceptionStack": null,
"data": false,
"success": true,
"exceptionName": null
}
Запрос выполнился без ошибок и jexl-скрипт вернёт значение false
.
Ограничения#
Время выполнения jexl-скрипта ограничено временем жизни сессии. Размер jexl-скрипта не ограничен.
REST сервис c обработкой http-запроса в прикладном пакете#
Введение#
В Global 3 SE Server реализован REST-сервис, позволяющий выполнять обработку HTTP-запроса в прикладном пакете, в контексте прикладной сессии.
Возможны 2 режима обработки запросов:
Exclusive Session - с сохранением состояния между запросами. Rest- и Gtk-сессии создаются при первом обращении клинета к сервису и закрываются по таймауту или при явном указании на необходимость закрытия сессий по завершению запроса.
Shared Session - без сохранения состояния между запросами. Каждый запрос обрабатывается в новых Rest- и Gtk-сессиях. Доступно с AS 1.14 RC7.
Пакет должен быть унаследован от одного из трейтов RestPkg
, RestESPkg
,
RestSSPkg
.
class Xxx_XxxPkg extends Pkg with RestPkg {
}
object Xxx_XxxPkg extends PkgFactory[Xxx_XxxPkg]{
}
Аутентификация#
Читайте в разделе Аутентификация в REST/SOAP-сервисах.
Методы RestPkg
/ RestESPkg
/ RestSSPkg
#
get(relativePath: String): AnyRef
Вызывается при поступлении GET запроса.
Допускается возврат:null
,None
,String
,ResponseBuilder
.post(relativePath: String): AnyRef
Вызывается при поступлении POST запроса.
Допускается возврат:null
,None
,String
,ResponseBuilder
.onActivate()
Вызывается при подключении новой gtk-сессииonDeactivate()
Вызывается при закрытии gtk-сессииbeforeReload(keyBundle: KeyBundle)
Вызывается перед перезагрузкой SBTafterReload(keyBundle: KeyBundle)
Вызывается после перезагрузки SBT и пересоздании gtk-сессииisSupportsSharedSession(): Boolean
Метод указывает, что пакет может обрабатывать запросы в режиме разделяемой GTK-сессии.isSupportsExclusiveSession(): Boolean
Метод указывает, что пакет может обрабатывать запросы в режиме эксклюзивной GTK-сессии.
Допустимые результаты методов get() и post()#
От типа возвращённого значения будет зависеть способ формирования ответа на Http-запрос.
null
,None
Ответ будет сформирован черезRestfulContext().get.responseBuilder
String
В ответ будет записано возвращённая строкаResponseBuilder
Ответ будет сформирован через возвращенныйResponseBuilder
. Он может быть равенRestfulContext().get.responseBuilder
, либо сформирован произвольным образом.Response
Ответом будет возвращенное значение.
Адреса#
Доступно с AS 1.14 RC7:
http://{server:port}/app/sys/rest/es/pkg/{Xxx_XxxxPkg}/{relativePath}
Доступно с AS 1.14 RC7:
http://{server:port}/app/sys/rest/ss/pkg/{Xxx_XxxxPkg}/{relativePath}
где:
{Xxx_XxxxPkg}
Имя прикладного пакета, унаследованного от RestfulPkg{relativePath}
Произвольный путь, который будет передан в методы get/post прикладного пакетаhttp://{server:port}
Адрес подключения к серверуsys
Системный прикладной модуль, в будущем будет app/sysrest
Шлюз для rest запросовes
/ss
Группировка по времени жизни gtk-сессии (Exclusive Session) / (Shared Session)pkg
Узел для доступа к пакетам.
Рабочее пространство#
Workspace
- Рабочее пространство.
Используется для возможности параллельной работы нескольких gtk сессий в рамках одного пользователя в es режиме.
Для предотвращения неконтролируемого разрастания сессий, количество сессий на пользователя в эксклюзивном режиме ограничено. На одного пользователя и один workspace может существовать только одна сессия.
Рабочее пространство задается в http заголовке:
Workspace
Имя рабочего пространства пользователя.
Exclusive Session#
Принцип работы сервиса#
Получение HTTP-запроса (GET или POST)
Проверка авторизационных данных пользователя.
Получение, захват существующего рабочего сеанса или создание нового.
Вызов метода прикладного пакета
get(…)
илиpost(…)
, соответственно
В данном методе производится формирование тела http-ответаОтправка ответа
Жизненный цикл http-сессий#
При обращении к rest - сервису.
Если cookie
JSESSIONID
не задан, создается новая http сессияИначе, используется сессия с переданным идентификатором
Если переданный id не корректный создается новая http сессияОбработка запроса
Возврат результата
При этом cookieJSESSIONID
будет содержать идентификатор http сессии
Время жизни http-сессии 15 минут. При отсутствии обращений к http-сессии в течении этого интервала, сессия уничтожается, и установленные в её атрибуты значения становятся не доступны.
Жизненный цикл gtk-сессий#
Gtk-сессия существует в разрезе 4-х параметров:
Алиас база данных
Получается из http-заголовкаDatabase
. Если заголовок не передан, используется алиас из конфигурационного файла сервера.Имя пользователя
Получается из авторизационных данных. Передаётся в http-заголовкеAuthorization
Имя прикладного пакета
Получается из строки адресаРабочее пространство
Получается из http-заголовкаWorkspace
. Если не задано, используетсяDefault
Одной http-сессии (одному JSESSIONID
) может соответствовать только одна
gtk-сессия. При обращении к другому пакету из одной http-сессии,
предыдущая gtk-сессия будет деактивирована и закрыта.
Таймаут gtk-сессии по умолчанию – 15 минут. Не используемая gtk-сессия
будет закрыта через 15 минут. Изменить таймаут или закрыть сессию по
завершению обработки запроса возможно через установку свойств
RestEXContext()
.
Создание новой сессии#
Происходит в случае, если:
в запрос не передан куки http сессии
или gtk сессия не существует в уникальном разрезе
Имя пользователя
База данных
Workspace
Пакет
При этом:
Создается gtk сессия
Вызываются методы прикладного пакета
onActivate
в соответствии с типом http запроса вызывается один из методов обработки запроса
get
post
В ответ добавляется кука http сессии
Работа в текущий сессии#
Происходит в случае, если:
в запрос передан куки http сессии
и gtk сессия существует в уникальном разрезе, и ее куки совпадает с переданным запросом
Таймаут сессии#
Происходит в случае, если:
в запрос передан куки не существующий http сессии
gtk сессии не существует
При этом
Происходит создание новой сессии и вызов метода обработки (смотри создание)
Возврат новой куки
Конфликт сессии#
Происходит в случае, если:
в запрос передан куки http сессии
gtk сессия существует в уникальном разрезе, и ее текущий куки не совпадает с куки http сессии
При этом
Генерируется ошибка захвата сессии
Захват сессии#
Происходит при условии:
в запрос не передан куки http сессии
или передан устаревший куки, который совпадает с куки в gtk сессии
gtk сессия существует в уникальном разрезе
При этом:
Вызываются методы прикладного пакета
onDeactivate
onActivate
в соответствии с типом http запроса вызывается один из методов обработки
Возвращается куки новой сессии
Доступ к данным REST-запроса из прикладного кода#
В прикладном коде Pkg-класса, выполняемом при REST-запросе, доступен
объект RestESContext
, предоставляющий доступ к данным REST-запроса и
REST-ответа. Для получения контекста, выполните:
val restContextOpt = RestESContext()
Методы контекста:
sessionTimeout: Long
Время жизни gtk-сессии после последнего запросаrequest: HttpServletRequest
Объект-запрос. Предоставляет доступ к параметрам, кукам и т.д.responseBuilder: Response.ResponseBuilder
Билдер ответа.markSessionClose(): Unit
Устанавливает флаг закрытия gtk-сессии по завершении обработки запросаisSessionClose: Boolean
Возвращает значение флага закрытия gtk-сессии
Обработка ошибок#
Коды ошибок#
500
– server error
Прикладное исключение409
- conflict
Конфликт сессий
Формат ошибок#
При возникновении ошибки, по умолчанию, ответ будет возвращён в формате xml.
Для изменения формата ответа, передайте http-параметр format
.
Доступные значения:
xml
json
Пример:
http://localhost:8080/app/sys/rest/es/pkg/Gs3_RestfulTestPkg/anypath?format=json
Xml ответ с ошибкой#
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
<response >
<status>%s</status>
<error>
<type>%s</type>
<message>%s</message>
<stacktrace><![CDATA[%s]]></stacktrace>
</error>
</response>
Json ответ с ошибкой#
{
"response": {
"status" : 0,
"data":{},
"error": {
"type": "" ,
"message": "",
"stacktrace": ""
}
}}
Пример обращения к сервису#
Java#
package ru.bitec.app.sys.rest;
import org.junit.Test;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PkgRestServiceTest0 {
private static String rootAddress = "http://localhost:8080/sys/rest/es";
private static Client client = ClientBuilder.newClient();
private Path testPath = Paths.get(System.getProperty("user.dir"), "src\\test\\java\\ru\\bitec\\app\\sys\\rest");
private Path cookiePath = testPath.resolve("JSESSIONID.cookie");
@Test
public void get_2_Cookie_test() throws Exception {
String JSESSION_Cookie = load_JSESSIONID_Cookie();
Invocation.Builder builder = client.target(rootAddress + "/pkg/Gs3_RestfulTestPkg/anypath").request()
//.header("Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes("UTF-8"), false))
.header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString("admin:admin".getBytes("UTF-8")))
.cookie(Cookie.valueOf(JSESSION_Cookie));
Response response = builder.get();
try {
save_JSESSIONID_Cookie(response);
String s = response.readEntity(String.class);
System.out.println("response: " + s);
} finally {
response.close();
}
}
private void save_JSESSIONID_Cookie(Response response) throws IOException {
if (response.getCookies().containsKey("JSESSIONID")) {
String cookie = response.getCookies().get("JSESSIONID").toString();
Files.write(cookiePath, cookie.getBytes(Charset.defaultCharset()));
}
}
private String load_JSESSIONID_Cookie() throws IOException {
if (Files.exists(cookiePath))
return new String(Files.readAllBytes(cookiePath), Charset.defaultCharset());
else
return null;
}
}
REST-сервис для взаимодействия с пользовательскими сессиями#
Сервис доступен только в режиме разработки. В проектных решениях не доступен.
Функциональность сервиса:
Получение списка сессий, удовлетворяющих условиям поиска.
Выполнение Jexl-скриптов в контексте гл.выборки приложения.
Получение списка сессий#
При отправке HTTP GET на адрес сервиса, будет возвращён JSON со списком сессий, удовлетворяющих условиям поиска. Http-запрос может содержать следующие параметры:
sbt
Фильтр по имени SBTclientId
Фильтр по идентификатору клиента (этот идентификатор присваивается каждому экземпляру браузера и содержится в куках)user
Фильтр по имени пользователяapp
Фильтр по имени главной выборки приложения (поиск осуществляется по вхождению переданного значения в полное имя гл.выбокрки)
Пример HTTP GET:
http://localhost:8080/app/sys/rest/sessions?sbt=test&user=admin&clientId=123456&app=Xxx_xxxxxx
Пример ответа:
{"sessions":[{
"sid": "E1",
"id": "02b5f602-ac2c-4259-9452-18840a1cd124",
"user": "admin",
"clientId": "B54E2C1F-04E5-4BB2-9372-A9AB8E9C6676",
"database": "PGTEST",
"app": "gtk-ru.bitec.app.gs3.Gs3_TestXmlApplication"
}]}
Выполнение Jexl в контексте главной выборки приложения#
Простой HTTP POST#
При отправке HTTP POST на адрес сервиса, в контексте гл.выборки приложения сессии, с переданным идентификатором, будет выполнен Jexl-скрипт, переданный в теле Http POST.
Адреса сервисов:
http://{server:port}/app/sys/rest/sessions/{id}/jexl/mainsel
Где{id}
- Идентификатор сессии. Значение{id}
можно получить из результата http get к сервису.http://{server:port}/app/sys/rest/sessions/new/jexl/mainsel?appname={имя_гл_выборки}
При этом будет запущена новая сессия. Для корректного запуска новой сессии и открытия приложения, необходимо передать имя главной выборки через http-параметрappname
Пример HTTP POST:
http://localhost:8080/app/sys/rest/sessions/949d77e9-80bf-4b93-bd3b-b424f8887783/jexl/mainsel
http://localhost:8080/app/sys/rest/sessions/new/jexl/mainsel?appname=Gs3_TestXmlApplication
В ответе вернётся JSON с результатом выполнения:
{
"sessionId":"14849330-3196-4b33-80c5-caa370186720",
"result":"[результат выполнения Jexl]"
}
Пример на Java#
@Test
public void post_test_1() throws Exception {
Map<String, Object> rootMap = (Map<String, Object>) Json.parse(doTestGET(rootAddress + "?sbt=test"));
List<Map<String, Object>> sessions = (List<Map<String, Object>>) rootMap.get("sessions");
if (sessions.isEmpty())
throw new Exception("No one ESession opened.");
Map<String, Object> session = sessions.get(0);
String id = (String) session.get("id");
String rootAddress = "http://localhost:8080/app/sys/rest/sessions";
doTestPOST(rootAddress + "/" + id + "/jexl/mainsel", "Some Jexl Script");
}
public String doTestGET(String uri, String database) throws Exception {
System.out.println("HTTP GET: " + uri);
Invocation.Builder builder = client.target(uri).request()
//.header("Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes("UTF-8"), false))
.header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString("admin:admin".getBytes("UTF-8")));
if (database != null) {
builder.header("Database", database);
}
String result = "";
Response response = builder.get();
try {
System.out.println("response status = " + response.getStatus());
System.out.println("response MediaType = " + response.getMediaType());
if (response.getStatus() == 200) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (InputStream inputStream = response.readEntity(InputStream.class)) {
StreamHelper.copyStream(inputStream, out);
}
byte[] bytes = out.toByteArray();
System.out.println("response: " + bytes.length + " bytes");
result = new String(bytes);
//System.out.println("filename: " + response.getHeaderString("Content-Disposition"));
} else {
result = response.readEntity(String.class);
}
} finally {
response.close();
}
System.out.println(result);
return result;
}
public String doTestPOST(String uri, String database, String body) throws Exception {
System.out.println("HTTP POST: " + uri);
Invocation.Builder builder = client.target(uri).request()
//.header("Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes("UTF-8"), false))
.header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString("admin:admin".getBytes("UTF-8")));
if (database != null) {
builder.header("Database", database);
}
String result = "";
Entity entity = Entity.entity(body, MediaType.TEXT_PLAIN_TYPE);
Response response = builder.post(entity);
try {
System.out.println("response status = " + response.getStatus());
System.out.println("response MediaType = " + response.getMediaType());
if (response.getStatus() == 200) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (InputStream inputStream = response.readEntity(InputStream.class)) {
StreamHelper.copyStream(inputStream, out);
}
byte[] bytes = out.toByteArray();
System.out.println("response: " + bytes.length + " bytes");
result = new String(bytes);
//System.out.println("filename: " + response.getHeaderString("Content-Disposition"));
} else {
result = response.readEntity(String.class);
}
} finally {
response.close();
}
System.out.println(result);
return result;
}
Form HTTP POST#
Альтернативным вариантом передачи Jexl скрипта, является отправка на адрес сервиса http формы, поле которой содержит Jexl-скрипт. Адреса сервисов:
http://{server:port}/app/sys/rest/form/sessions/{id}/jexl/mainsel
http://{server:port}/app/sys/rest/form/sessions/new/jexl/mainsel?appname={имя_гл_выборки}
В ответе вернётся команда перенаправления на страницу входа с идентификатором сессии в качестве http-параметра.
Пример HTML-страницы.#
<html>
<body onload="onLoadFunc()">
<script>
function onLoadFunc() {
const form = document.createElement('form');
form.method = 'POST';
form.id = 'MultilinePostForm'
form.action = 'http://localhost:8080/app/sys/rest/form/sessions/new/jexl/mainsel?appname=Gs3_TestXmlApplication';
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = 'script';
hiddenField.value = `Btk_ClassAvi.list().newForm().open();
Gs3_RootTestAvi.list().newForm().open();`;
form.appendChild(hiddenField);
document.body.appendChild(form);
form.submit();
}
</script>
</body>
</html>
Сервис отчётов#
Сервис предназначен для формирования печатных документов, средствами сервера Global 3, по запросу из внешних систем.
Для получения файла со сформированным отчётом, необходимо отправить HTTP GET по адресу сервиса. Адреса сервисов:
http://{server:port}/app/sys/rest/report/{name}
http://{server:port}/app/sys/rest/report/{name}/{date}
где:
{name}
Системное имя печатной формы{date}
Дата версии отчёта
Пример:
http://localhost:8080/app/sys/rest/report/Rpt_JasperSimpleQuery/15.02.2019
Аутентификация#
Читайте в разделе Аутентификация в REST/SOAP-сервисах.
Параметризация отчёта#
Для передачи параметров в формируемый отчёт, передавайте значения через http-параметры. Служебные символы и пробелы должны бить заменены Escape-символами.
Пример:
http://localhost:8080/app/sys/rest/report/Rpt_JasperSimpleQuery?param1=value1¶m2=value2
Аутентификация в REST/SOAP сервисах#
Сервисы используют HTTP-аутентификацию двух типов: Basic и Bearer.
Basic - c использованием логина, пароля и имени базы#
При 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 - c использованием токена аутентификации#
При 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 Tokengjwt
- Gtk Json Web Token
Application Server Token#
Токен формируется сервером приложений после входа в приложение Глобал с использованием имени пользователя и пароля. Токен возвращется клиенту в Cookie access_token, привязанной к подмножеству адресов http[s]://server/{DATABASE}/
. Эта Cookie автоматически присоединяется ко всем запросам по адресам http[s]://server/{DATABASE}/
, выполняемым из браузера, аутентифицированного в системе Глобал.
Токен устаревает после перезапуска сервера приложений.
Gtk Json Web Token#
Токен, по стандарту JWT, формируется в модуле GTK (или на внешнем серсисе) и валидируется в модуле GTK.
GJWT разделяются на подтипы
UserHash
- Долгоживущий токен пользователяUserCrt
- Подписанный пользователем токенProxyCrt
- Подписанный прокси-пользователем токен
Долгоживущий токен пользователя#
Формируется администратором системы Глобал и сообщается пользователю любым удобным способом, исключающим утечку токена.
Токен, сопоставленный с учётной записью пользователя, хранится в БД решения.
Срок годности - определяется администратором.
Тело JWT (payload) должно содержать следующие поля:
typ
-"UserHash"
sub
- имя пользователя, под которым необходимо аутентифицироватьсяexp
- дата устаревания
При проверке валидности токена, проверяется существование токена в списке сопоставленных с учётной записью токенов и дата устаревания.
Подписанный пользователем токен#
Формируется пользователем и подписывается закрытым RSA-ключом пользователя.
Открытый RSA-ключ, сопоставленный с учётной записью пользователя, хранится в БД решения.
Срок годности - определяется пользователем.
Тело JWT (payload) должно содержать следующие поля:
typ
-"UserCrt"
sub
- имя пользователя, под которым необходимо аутентифицироватьсяcid
- идентификатор открытого ключа в системе Глобал, сопоставленного с пользователемexp
- дата устаревания
При проверке валидности токена, на основе полей sub
и cid
из БД решения получается открытый RSA-ключ и проверяется валидность подписи JWT.
Подписанный прокси-пользователем токен#
Прокси-аутентификация - это аутентификация под именем пользователя ``№2`` от имени пользователя ``№1``,
именуемого ``прокси-пользователем``.
Данный вид аутентификации может быть использован внешними планировщиками задач, которые необходимо выполнять под разными пользователями.
При этом, планировщику нет необходимости хранить и передавать секретные учётные данные пользователей.
Формируется прокси-пользователем и подписывается закрытым RSA-ключом прокси-пользователя.
Открытый RSA-ключ, сопоставленный с учётной записью прокси-пользователя, хранится в БД решения.
Срок годности - определяется прокси-пользователем.
Тело JWT (payload) должно содержать следующие поля:
typ
-"ProxyCrt"
sub
- имя пользователя, под которым необходимо аутентифицироватьсяpsub
- имя прокси-пользователя, которому принадлежитам закрытый и открыты ключиcid
- идентификатор открытого ключа в системе Глобал, сопоставленного с прокси-пользователем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-сервисов#
Класс Btk_AcPackage содержит информацию об администрируемых пакетах:
Имя пакета
id модуля, которому он принадлежит
является ли он rest-пакетом (
bIsRest
)администрируются ли серверные полномочия (
bControlServerPriv
)
Пакет считается Rest-пакетом, если он наследуется от трейта RestPkg, в том числе от его потомков. Rest-пакеты регистрируются при обновлении администрируемых объектов по модулю. Если при обновлении окажется, что пакет больше не наследуется от RestPkg, то признак bIsRest
в таблице будет снят.
Внимание
После изменения статуса администрируемости пакета, необходимо сбросить shared-кэш по классу Btk_AcPackage.
Выдача прав на вызов Rest-пакетов#
В карточке роли на закладке «Права на Rest-пакеты
» отображен список пакетов из класса Btk_AcPackage и имеет ли роль доступ к ним. Информация по доступу для роли хранится в таблице Btk_AcRolePackagePriv. Для выбора\снятия доступа роли к пакету используйте чекбокс Имеет доступ
. Во время индексации прав пользователей по роли для всех Rest-пакетов, у которых стоит галка «Контролировать серверные полномочия», будут выданы объектные привилегии пользователям, которые имеют данный профиль. Привилегии регистрируются на адм. объект Btk_AcPackage.
Примечание
Супер-пользователь имеет полный доступ
Администрирование SOAP-вызовов#
Администрирование SOAP-вызовов включается в Администратор \ Настройки > Настройки администрирования
соответствующим чекбоксом. Для выдачи права роли необходимо дать доступ к объектной привилегии UseSoap
адм. объекта Btk_ManagementPkg.
Примечание
Супер-пользователь имеет полный доступ