Создайте подписанный запрос SAML2 LogOut - PullRequest
14 голосов
/ 16 ноября 2011

Моя цель - реализовать протокол единого выхода.Сначала я понимаю, как работает стандарт и как я могу его использовать в моем сценарии: ADFS 2.0 в качестве IdP , для меня это как «черный ящик»

На данный момент я делаю следующее:

  1. Отправить <AuthnRequest> моему IdP

  2. IdP запрашивает учетные данные, япредоставьте их и успешно войдите в систему.

  3. Получите значение SessionIndex из формы и создайте <LogoutRequest>

<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_135ad2fd-b275-4428-b5d6-3ac3361c3a7f" Version="2.0" Destination="https://idphost/adfs/ls/" IssueInstant="2008-06-03T12:59:57Z"><saml:Issuer>myhost</saml:Issuer><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" NameQualifier="https://idphost/adfs/ls/">myemail@mydomain.com</NameID<samlp:SessionIndex>_0628125f-7f95-42cc-ad8e-fde86ae90bbe</samlp:SessionIndex></samlp:LogoutRequest>

  1. Возьмите вышеуказанное <LogoutRequest> и закодируйте его в Base64

  2. Создает следующую строку: SAMLRequest=base64encodedRequest&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1

  3. С помощью приведенной выше строки генерируется подпись

  4. Кодировать подпись в base64

  5. Отправить запрос: https://"https://idphost/adfs/ls/?SAMLRequest=base64encodedRequest&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=base64EncodedSignature

Но IdP отвечает мне: проверка подписи сообщения SAML не удалась.

Для подписи я использую свой закрытый ключ (2048 байт) и для проверкипредполагается, чтоIdP использует мой открытый ключ (тот, который я отправил при регистрации хоста)

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

// Retrieve the private key
KeyStore keyStore = KeyStore.getInstance("JKS", "SUN");
FileInputStream stream;
stream = new FileInputStream("/path/to/my/keystore.jks");
keyStore.load(stream, "storepass".toCharArray());
PrivateKey key = (PrivateKey) keyStore.getKey("keyAlias","keyPass".toCharArray());

// Create the signature
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(key);
signature.update("SAMLRequest=jVJda8IwFH2e4H8ofW%2BbVmvboGWCDApusDn2sBdJm1sNtEmXmw7x1y92KDrY2Ov5uueEzJG1TUfXaqd68wIfPaBxDm0jkQ7Mwu21pIqhQCpZC0hNRTfLxzWNfEI7rYyqVONeWf52METQRijpOsVq4W7JoSzjJJnWAEAmwLMMpmRG0jCrYJICIcR13kCjdSxcG%2BA6K9tQSGYGZG9MhzQIGrUT0uPw6VegpV%2FtA8ZrDBq0ZxB7KCQaJo2NICT1yMwjk9cwonFG4%2BTdzceju%2FmpOx3EOu8qYThgGJ3j5sE1fZE%2F2X3FynlQumXm9%2BGhHw6I4F49SCm0TDRLzjWgrXiKee5ZI2oB%2Bj%2Bj8qYX6GvFtdj1cPRryzPJ4Xh%2F2%2Fe736VvRzf2nn24wmoP%2BZbMojSM4tpL6iz2plFVeYyn4NUc0hmDjJQlfCf9cI5HZ%2Fjm4%2BRf&RelayState=null&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1".getBytes());

String signatureBase64encodedString = (new BASE64Encoder()).encodeBuffer(signature.sign());

Ответы [ 3 ]

10 голосов
/ 21 ноября 2011

Наконец-то я получил правильный рецепт:

  1. Создание значения SAMLRequest
  2. Кодировать значение SAMLRequest в Base64
  3. URL-кодирование значения SAMLRequest
  4. URL-кодирование значения SigAlg: http://www.w3.org/2000/09/xmldsig#rsa-sha1
  5. Введите подпись алгоритма (SHA1withRSA) с помощью SAMLRequest = значение & SigAlg = значение
  6. URL-кодирование сгенерированной подписи

Мы можем выполнить шаги 2 и 3 с помощью отладчика SAML 2.0 (https://rnd.feide.no/simplesaml/module.php/saml2debug/debug.php). А для URL-кодирование используют классические w3schools (http://www.w3schools.com/tags/ref_urlencode.asp)

Внимание! Убедитесь, что алгоритм вашей проверяющей стороны в ADFS2 настроен на SHA1!

С уважением,

Луис

PS: теперь я должен немного кодировать ...

pps: Вы можете найти код здесь: https://github.com/cerndb/wls-cern-sso/tree/master/saml2slo

2 голосов
/ 24 апреля 2014

Есть ошибка в реализации ADFS , когда сообщение об ошибке выдает в обратном направлении.Когда это говорит:

Запрос SAML не подписан с ожидаемым алгоритмом подписи.Запрос SAML подписан с использованием алгоритма подписи http://www.w3.org/2001/04/xmldsig-more#rsa-sha256.Ожидаемый алгоритм подписи - http://www.w3.org/2000/09/xmldsig#rsa-sha1

, это фактически означает, что вы используете SHA1 и ожидали SHA256.

0 голосов
/ 16 марта 2017

Поскольку у нас было много шагов, чтобы наконец успешно реализовать SLO на Domino 9.0.1, я решил написать код, который позволит использовать любую (будущую) конфигурацию IdP для работы с нашими серверами Domino. Я реализовал следующую стратегию:

  • Используйте как можно больше информации из входящего запроса на выход из SAML
  • Определите конфигурацию IdP в idpcat.nsf, чтобы найти соответствующую информацию об ответе IdP SLO, который должен быть отправлен поставщику услуг IdP (сервер SAML)
  • Определите Ответ выхода из SAML в соответствующей конфигурации IdP в idpcat.nsf, чтобы разрешить динамическую адаптацию к новым требованиям в случае изменения конфигурации SAML.

В результате код считывает все поля входящего запроса на выход из SAML в карту параметров, декодирует и раздувает строку запроса для извлечения XML-параметров запроса в карту параметров. Поскольку разные веб-сайты на сервере домино могут быть настроены для разных поставщиков услуг IdP, чтобы разрешить SSO-соединение, я идентифицирую конфигурацию IdP с соответствующим «именем хоста» и считываю все ее поля в одной и той же карте параметров. Для определения применимого XML-ответа я решил записать все необходимые определения в комментарий конфигурации IdP, который позволяет адаптировать отдельные конфигурации IdP для использования одного и того же кода для разных поставщиков IdP, даже если используются разные версии SAML. Определения в поле «Комментарии» конфигурации IdP в idpcat.nsf выглядят следующим образом:

Ответ SLO: /idp/SLO.saml2;

XML ответа SLO: "<" urn: LogoutResponse ID = "@ UUID" Version = "# Version" IssueInstant = "@ ACTUAL_TIME" Destination = "SLO_Response" InResponseTo = "# ID" xmlns: urn = "# xmlns: урна «>» "<" urn1: Issuer xmlns: urn1 = "XML_Parameter1" ">« HTTP_HSP_LISTENERURI "<" / urn1: Issuer ">" "<" Урна: Status ">" "<" urn: StatusCode Value = "XML_Parameter2" / ">" "<" / Урна: Status ">" "<" / Урна: LogoutResponse ">";

Значения XML: #xmlns: urn = протокол -> утверждение & # xmlns: urn = протокол -> статус: успех;

Параметры ответа: RelayState & SigAlg & Signature;

Тип подписи: SHA256 с RSA;

Тип хранилища ключей: PKCS12;

Файл хранилища ключей: D: \ saml_cert.pfx;

Пароль хранилища ключей: **********;

Сертификат: {xxxxxxxxxx}

Ключи в этих определениях отделяются от значений с помощью «:», а конец значений указывается с помощью «;» (не новая строка) Это позволяет настроить полную параметризацию ответа SAML в соответствии с требованиями поставщика услуг IdP в соответствующей конфигурации IdP, используемой для соединения SSO. Определения указаны следующим образом:

• Ответ SLO: это относительный адрес, на который необходимо отправить ответ SLO на соответствующем сервере IdP.

• XML ответа SLO: это текстовая строка, определяющая ответ SLO, структурированный в формате XML (используйте «<» и «>« без »). Строки, идентифицирующие параметры, найденные в карте параметров, заменяются их соответствующими значениями. Убедитесь, что аналогичные параметры определены правильно. Параметры Cookie имеют начальный «$», а параметры XML запроса-запроса - «#». Дополнительно предоставляются 2 формулы, где «@UUID» будет вычислять случайный UUID с правильным формат для параметра ID ответа XML и «@ACTUAL_TIME» вычислит правильную метку времени в формате Instant для параметра IssueInstant ответа XML.

• Значения XML: эта текстовая строка идентифицирует дополнительные параметры, где в основном используется известный параметр, но часть значения параметра необходимо заменить для соответствия требуемому тексту.Параметры обозначаются строкой «XML_Paramater», за которой следует позиция в строке, разделяющей каждое значение символом «&» в тексте XML ответа SLO.Текст для значений XML структурирован таким образом, что за идентификатором параметра следует «=», за заменяемым текстом следует «->» и новый текст.

• Параметры ответа: параметры ответа разделяютсяс "&" и будет добавлен в ответ SLO, как определено.Если требуется подпись, параметры SigAlg и Signature необходимы в этой строке и должны быть помещены в конце.

• Тип подписи: Если требуется подпись, здесь указывается тип алгоритма, используемого для вычисления подписи.

• Тип хранилища ключей: это тип хранилища ключей, используемого для сертификата.

• Файл хранилища ключей: это файл, в котором сохранено хранилище ключей, включая диск и путь к Lotus.Сервер Notes.Мы использовали D: \ saml_cert.pfx на тестовом сервере.

• Пароль хранилища ключей: это пароль, необходимый для открытия файла хранилища ключей и сохраненных в нем сертификатов.

• Сертификат: этоПсевдоним сертификата, идентифицирующий сертификат в файле хранилища ключей.Если сертификат хранится в новом файле хранилища ключей для объединения нескольких сертификатов в одном месте, псевдоним всегда изменяется на новое значение, которое необходимо здесь адаптировать.

Код, который я реализовал, - это агент Java симя «Logout» в domcfg.nsf, но оно может быть в основном реализовано в любой базе данных, доступной для пользователей единого входа, и работает как сервер, чтобы обеспечить защиту конфигураций IdP в idpcat.nsf с максимальной безопасностью.В сервис-провайдере IdP вы должны настроить SLO-запрос для сервера Domino и соответствующего веб-сайта как «https://WEBSITE/domcfg.nsf/Logout?Open&", за которым следует запрос SAML. Если подписчик запрашивается провайдером IdP-сервиса, вам необходимо сохранить KeyStore.Файл с сертификатом, включая PrivateKey, необходимый для подписи. Управлять файлом KeyStore можно с помощью функции оснастки MMC (см. https://msdn.microsoft.com/en-us/library/ms788967(v=vs.110).aspx).. С помощью функции экспорта можно объединить несколько сертификатов в один файл, но вы можетенеобходимо убедиться, что вы экспортируете закрытые ключи в файл с помощью соответствующей настройки в мастере экспорта.

Это код для агента «Выход из системы», который выходит из системы пользователя с сервера домино и отправляетответ SAML Logout для поставщика услуг IdP:

import lotus.domino.*;
import java.io.*;
import java.util.*;
import java.text.*;
import com.ibm.xml.crypto.util.Base64;
import java.util.zip.*;
import java.net.URLEncoder;
import java.security.*;

public class JavaAgent extends AgentBase {
    public void NotesMain() {
        try {
            Session ASession = getSession();
            AgentContext AContext = ASession.getAgentContext();
            DateTime date = ASession.createDateTime("Today 06:00");
            int timezone = date.getTimeZone();

            Database DB = AContext.getCurrentDatabase();
            String DBName = DB.getFileName();
            DBName = DBName.replace("\\", "/").replace(" ", "+");

            //Load PrintWriter to printout values for checking (only to debug)
            //PrintWriter pwdebug = getAgentOutput();
            //pwdebug.flush();

            //Load Data from Logout Request
            Document Doc = AContext.getDocumentContext();
            Vector<?> items = Doc.getItems();
            Map<String, String> Params = new LinkedHashMap<String, String>();
            for (int j=0; j<items.size(); j++) {
                Item item = (Item)items.elementAt(j);
                if (!item.getValueString().isEmpty()) Params.put(item.getName(), item.getValueString());
            }
            String ServerName = Params.get("HTTP_HSP_HTTPS_HOST");
            int pos = ServerName.indexOf(":");
            ServerName = pos > 0 ? ServerName.substring(0, ServerName.indexOf(":")) : ServerName;
            Params.put("ServerName", ServerName);
            Doc.recycle();
            DB.recycle();

            //Load Cookie Variables
            Params = map(Params, Params.get("HTTP_COOKIE"), "$", "; ", "=", false, false);
            //Load Query Variables
            Params = map(Params, Params.get("QUERY_STRING_DECODED"), "", "&", "=", false, false);
            //Decode and Infalte SAML Request
            String RequestUnziped = decode_inflate(Params.get("SAMLRequest"), true);
            //pwdebug.println("Request unziped: " + RequestUnziped);
            //System.out.println("Request unziped: " + RequestUnziped);
            String RequestXMLParams = RequestUnziped.substring(19, RequestUnziped.indexOf("\">"));
            //Load XML Parameters from Request
            Params = map(Params, RequestXMLParams, "#", "\" ", "=\"", false, false);
            //for (Map.Entry<String, String> entry : Params.entrySet()) pwdebug.println(entry.getKey() + " value: " + entry.getValue());
            //for (Map.Entry<String, String> entry : Params.entrySet()) System.out.println(entry.getKey() + " value: " + entry.getValue());
            String Issuer = RequestUnziped.substring(RequestUnziped.indexOf(":Issuer"), RequestUnziped.indexOf("Issuer>"));
            Issuer = Issuer.substring(Issuer.indexOf(">") + 1, Issuer.indexOf("<"));
            Params.put("SLO_Issuer", Issuer);

            //Load Parameters for the Response
            DbDirectory Dir = ASession.getDbDirectory(null);
            Database idpcat = Dir.openDatabase("idpcat.nsf");
            View idpView = idpcat.getView("($IdPConfigs)");
            Document idpDoc = idpView.getDocumentByKey(ServerName, false);
            items = idpDoc.getItems();
            for (int j=0; j<items.size(); j++) {
                Item item = (Item)items.elementAt(j);
                if (!item.getValueString().isEmpty()) Params.put(item.getName(), item.getValueString());
            }
            Params = map(Params, idpDoc.getItemValueString("Comments"), "", ";", ": ", false, false);
            Params.put("SLO_Response", Issuer + Params.get("SLO Response"));
            Params.put("@UUID", "_" + UUID.randomUUID().toString());
            Params.put("@ACTUAL_TIME", actualTime(Params.get("#IssueInstant"), Params.get("#NotOnOrAfter"), timezone));
            //for (Map.Entry<String, String> entry : Params.entrySet()) pwdebug.println(entry.getKey() + " value: " + entry.getValue());
            //for (Map.Entry<String, String> entry : Params.entrySet()) System.out.println(entry.getKey() + " value: " + entry.getValue());
            idpDoc.recycle();
            idpView.recycle();
            idpcat.recycle();
            Dir.recycle();

            //Setup XML Response as defined
            String ResponseString = Params.get("SLO Response XML");
            for (Iterator<String> itRq = Params.keySet().iterator(); itRq.hasNext();) {
                String Key = (String) itRq.next();
                ResponseString = ResponseString.replace(Key, Params.get(Key));
            }
            //pwdebug.println("Response String replaced: " + ResponseString);
            //System.out.println("Response String replaced: " + ResponseString);
            //Load Values to be exchanged in the defined Response
            Map<String, String> RsXMLValues = map(new LinkedHashMap<String, String>(), Params.get("XML Values"), "", "&", "=", true, false);
            //for (Map.Entry<String, String> entry : RsXMLValues.entrySet()) pwdebug.println(entry.getKey() + " value: " + entry.getValue());
            //for (Map.Entry<String, String> entry : RsXMLValues.entrySet()) System.out.println(entry.getKey() + " value: " + entry.getValue());
            //Exchange defined Strings with Values from the Request
            int itc = 0;
            for (Iterator<String> itRXV = RsXMLValues.keySet().iterator(); itRXV.hasNext();) {
                itc = itc + 1;
                String Key = (String) itRXV.next();
                int lock = Key.indexOf(" -> ");
                String KeyRq = lock > 0 ? Key.substring(0, lock) : Key;
                int lockRq = KeyRq.indexOf(" ");
                KeyRq = lockRq > 0 ? KeyRq.substring(0, lockRq) : KeyRq;
                String Parameter = Params.get(KeyRq);
                String Value = RsXMLValues.get(Key);
                if (!Value.isEmpty()) {
                    int locv = Value.indexOf(" -> ");
                    String ValueS = locv > 0 ? Value.substring(0, locv) : Value;
                    String ValueR = locv > 0 && Value.length() > locv + 4 ? Value.substring(locv + 4) : ValueS;
                    Parameter = Parameter.replace(ValueS, ValueR);
                }
                ResponseString = ResponseString.replace(("XML_Parameter" + itc), Parameter);
            }
            //pwdebug.println("Final XML Response String: " + ResponseString);
            //System.out.println("Final XML Response String: " + ResponseString);
            //Deflate and Encode the XML Response
            String ResponseZiped = deflate_encode(ResponseString, Deflater.DEFAULT_COMPRESSION, true);
            //pwdebug.println("Response Ziped: " + ResponseZiped);
            //System.out.println("Response Ziped: " + ResponseZiped);
            //Setup Response URLQuery as defined
            String ResponseEncoded = "SAMLResponse=" + URLEncoder.encode(ResponseZiped, "UTF-8");
            //pwdebug.println("Response to Sign: " + ResponseEncoded);
            //System.out.println("Response to Sign: " + ResponseEncoded);
            //Load Parameters to be added to the Response
            Map<String, String> ResponseParams = map(new LinkedHashMap<String, String>(), Params.get("Response Parameters"), "", "&", "=", false, true);
            //for (Map.Entry<String, String> entry : ResponseParams.entrySet()) pwdebug.println(entry.getKey() + " value: " + entry.getValue());
            //for (Map.Entry<String, String> entry : ResponseParams.entrySet()) System.out.println(entry.getKey() + " value: " + entry.getValue());
            //Add defined Parameters with Values from the Request
            for (Iterator<String> itRP = ResponseParams.keySet().iterator(); itRP.hasNext();) {
                String Key = (String) itRP.next();
                if (Key.contains("Signature")) {
                    //pwdebug.println("Response to Sign: " + ResponseEncoded);
                    //System.out.println("Response to Sign: " + ResponseEncoded);
                    Signature signature = Signature.getInstance(Params.get("Signature Type"));
                    //pwdebug.println("Signature: Initiated");
                    //System.out.println("Signature: Initiated");
                    KeyStore keyStore = KeyStore.getInstance(Params.get("KeyStore Type"));
                    //pwdebug.println("Key Store: Initiated");
                    //System.out.println("Key Store: Initiated");
                    keyStore.load(new FileInputStream(Params.get("KeyStore File")), Params.get("KeyStore Password").toCharArray());
                    //pwdebug.println("Key Store: Loaded");
                    //System.out.println("Key Store: Loaded");
                    PrivateKey key = (PrivateKey) keyStore.getKey (Params.get("Certificate"), Params.get("KeyStore Password").toCharArray());
                    //pwdebug.println("Key Store: Private Key Loaded");
                    //System.out.println("Key Store: Private Key Loaded");
                    signature.initSign(key);
                    //pwdebug.println("Signature: Private Key Initiated");
                    //System.out.println("Signature: Private Key Initiated");
                    signature.update(ResponseEncoded.getBytes("UTF-8"));
                    //pwdebug.println("Signature: Signed");
                    //System.out.println("Signature: Signed");
                    String ResponseSignature = URLEncoder.encode(Base64.encode(signature.sign()), "UTF-8"); 
                    //pwdebug.println("Signature: Signed");
                    //System.out.println("Signature: Signed");
                    ResponseEncoded = ResponseEncoded.concat("&").concat(Key).concat("=").concat(ResponseSignature);
                }
                else ResponseEncoded = ResponseEncoded.concat("&").concat(Key).concat("=").concat(URLEncoder.encode(Params.get(Key), "UTF-8"));
            }
            String ResponseURL = Params.get("SLO_Response").concat("?").concat(ResponseEncoded);
            //pwdebug.println("Final Response URL: " + ResponseURL);
            //pwdebug.close();
            //System.out.println("Final Response URL: " + ResponseURL);

            //Send Logout to Server and redirect to Response to defined Destination
            PrintWriter pwsaml = getAgentOutput();
            pwsaml.flush();
            pwsaml.println("[" + Params.get("HTTP_HSP_LISTENERURI") + "/" + DBName + "?logout&redirectto=" + URLEncoder.encode(ResponseURL, "UTF-8") + "]");
            pwsaml.close();

            //Recycle Agent and Session
            AContext.recycle();
            ASession.recycle();

        } catch(Exception e) {
            PrintWriter pwerror = getAgentOutput();
            pwerror.flush();
            pwerror.println(e);
            System.out.println(e);
            pwerror.close();
        } 
    }

    //Load Maps from Strings to identify Paramteres and Values
    private static Map<String, String> map(Map<String, String> map, String input, String keys, String spliting, String pairing, Boolean keycount, Boolean empty) {
        Map<String, String> output = map.isEmpty() ? new LinkedHashMap<String, String>() : map;
        String[] Pairs = input.split(spliting);
        int kc = 0;
        for (String Pair : Pairs) {
            kc = kc + 1;
            int pos = Pair.indexOf(pairing);
            String Key = pos > 0 ? Pair.substring(0, pos) : Pair;
            if (keycount) Key = Key + " " + kc;
            String Value = pos > 0 && Pair.length() > (pos + pairing.length()) ? Pair.substring(pos + pairing.length()) : "";
            if (!output.containsKey(Key) && (empty || !Value.trim().isEmpty())) output.put((keys + Key).trim(), Value.trim());
        }
        return output;
    }

    //Decode and Inflate to XML
    private static String decode_inflate(String input, Boolean infflag) throws IOException, DataFormatException {
        byte[] inputDecoded = Base64.decode(input.getBytes("UTF-8"));
        Inflater inflater = new Inflater(infflag);
        inflater.setInput(inputDecoded);
        byte[] outputBytes = new byte[1024];
        int infLength = inflater.inflate(outputBytes);
        inflater.end();
        String output = new String(outputBytes, 0, infLength, "UTF-8");
        return output;
    }

    //Deflate and Encode XML
    private static String deflate_encode(String input, int level , Boolean infflag) throws IOException {
        byte[] inputBytes = input.getBytes("UTF-8");
        Deflater deflater = new Deflater(level, infflag);
        deflater.setInput(inputBytes);
        deflater.finish();
        byte[] outputBytes = new byte[1024];
        int defLength = deflater.deflate(outputBytes);
        deflater.end();
        byte[] outputDeflated = new byte[defLength];
        System.arraycopy(outputBytes, 0, outputDeflated, 0, defLength);
        String output = Base64.encode(outputDeflated);
        return output;
    }

    //Define Date and Time Formats
    private static SimpleDateFormat DateFormat = new SimpleDateFormat("yyyy-MM-dd");
    private static SimpleDateFormat TimeFormat = new SimpleDateFormat("HH:mm:ss.SSS");

    //Formated Actual Time
    private static String actualTime(String minTime, String maxTime, int localZone) throws ParseException {
        Date actualtime = new Date();
        long acttime = actualtime.getTime();
        long mintime = resetTime(minTime, localZone);
        long maxtime = resetTime(maxTime, localZone);
        acttime = (acttime > mintime) && (acttime < maxtime) ? acttime: mintime + 1000;
        return formatTime(acttime);
    }

    //Reset timemillis from String as defined
    private static long resetTime(String givenTime, int localZone) throws ParseException {
        Date date = DateFormat.parse(givenTime.substring(0, givenTime.indexOf("T")));
        long days = date.getTime();
        Date time = TimeFormat.parse(givenTime.substring(givenTime.indexOf("T") + 1, givenTime.indexOf("Z")));
        long hours = time.getTime();
        long zonecorr = localZone * 3600000;
        return days + hours - zonecorr;
    }

    //Format timemillis into a String as required
    private static String formatTime(long totalmilliSeconds) {
        long date = 86400000 * (totalmilliSeconds / 86400000);
        long time = totalmilliSeconds % 86400000;
        String dateString = DateFormat.format(date).concat("T");
        String timeString = TimeFormat.format(time).concat("Z");
        return dateString.concat(timeString);
    }

    public static String noCRLF(String input) { 
        String lf = "%0D";
        String cr = "%0A";
        String find = lf;
        int pos = input.indexOf(find);
        StringBuffer output = new StringBuffer();
        while (pos != -1) {
            output.append(input.substring(0, pos));
            input = input.substring(pos + 3, input.length());
            if (find.equals(lf)) find = cr;
            else find = lf;
            pos = input.indexOf(find);
        }
        if (output.toString().equals("")) return input;
        else return output.toString();
    }
}

Как вы могли бы узнать, несколько откомментированных строк можно использовать для отладки агента, если определения не верны и не приводят к успешному выходу из системы.. Вы можете легко изменить эти линииЕсли вы удалите «//», начиная эти строки, и распечатайте параметры, которые вы хотели бы видеть на экране, или отправьте их в журналы.

Чтобы инициировать SLO на сервере домино, я написал другой Java-агентиспользуя ту же концепцию.Агент называется startSLO и находится в той же базе данных, что и агент «Выход из системы».Использование этого агента может быть легко реализовано в любом приложении, создавая кнопки, открывающие соответствующий URL-адрес «/domcfg.nsf/startSLO?Open».Агент "startSLO" имеет следующий код:

1063 *
...