构造签名的SAML2 LogOut请求

时间:2011-11-16 10:25:39

标签: saml-2.0

我的目标是实现单一登出协议。首先,我了解标准如何工作以及如何在我的方案中使用它: ADFS 2.0作为IdP ,对我来说就像一个“黑盒子”

我现在正在做的是下一步:

  1. 向我的IdP发送<AuthnRequest>

  2. IdP要求我提供凭据,我提供这些凭据并成功登录。

  3. 从中获取SessionIndex值并构造<LogoutRequest>

  4. <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

    6. 但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 个答案:

答案 0 :(得分:10)

最后我得到了正确的食谱:

  1. 生成SAMLRequest值
  2. 在Base64中编码SAMLRequest值
  3. 对SAMLRequest值进行URL编码
  4. 对SigAlg值进行URL编码: http://www.w3.org/2000/09/xmldsig#rsa-sha1
  5. 输入算法签名(SHA1withRSA) SAMLRequest =值安培; SigAlg =值
  6. 对生成的签名进行URL编码
  7. 我们可以使用SAML 2.0调试器执行步骤2和3 (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

答案 1 :(得分:2)

其中a bug in the ADFS implementation所提供的错误消息是向后的。当它说:

  

SAML请求未使用预期的签名算法进行签名。 SAML请求使用签名算法http://www.w3.org/2001/04/xmldsig-more#rsa-sha256签名。预期签名算法为http://www.w3.org/2000/09/xmldsig#rsa-sha1

它实际上意味着您正在使用SHA1并且它期待SHA256。

答案 2 :(得分:0)

由于我们已经采取了许多步骤来最终在Domino 9.0.1上成功实现SLO,因此我决定编写代码,允许使用任何(未来)IdP配置与Domino服务器一起运行。我实施了以下策略:

  • 尽可能多地使用传入SAML注销请求中提供的信息
  • 识别idpcat.nsf中的IdP配置,以查找有关要发送给IdP服务提供商(SAML服务器)的IdP SLO响应的相应信息
  • 在idpcat.nsf中的相应IdP配置中定义SAML注销响应,以便在SAML配置更改时动态适应新要求。

因此,代码将传入SAML注销请求的所有字段读入参数映射,并对查询字符串进行解码和膨胀,以将请求的XML参数提取到参数映射中。由于可以为不同的IdP服务提供商配置多米诺骨牌服务器上的不同网站以允许SSO连接,因此我使用相应的&#34;主机名&#34;来识别IdP配置。并在同一参数图中读取其所有字段。为了定义适用的XML响应,我决定将所有需要的定义写入IdP配置的注释中,这允许调整单个IdP配置以便为不同的IdP提供者使用相同的代码,即使使用不同的SAML版本。 idpcat.nsf中IdP配置的Comment字段中的定义如下所示:

SLO响应:/ idp / SLO.saml2;

SLO响应XML:&#34;&lt;&#34; urn:LogoutResponse ID =&#34; @ UUID&#34;版本=&#34;#版本&#34; IssueInstant =&#34; @ ACTUAL_TIME&#34;目的地=&#34; SLO_Response&#34; InResponseTo =&#34;#ID&#34;的xmlns:瓮=&#34; #xmlns:瓮&#34;&GT;&#34;     &#34;&lt;&#34; urn1:Issuer xmlns:urn1 =&#34; XML_Parameter1&#34;&#34;&gt;&#34; HTTP_HSP_LISTENERURI&#34;&lt;&#34; / urn1:Issuer&# 34;&GT;&#34;     &#34;&LT;&#34;瓮:状态&#34;&GT;&#34;         &#34;&lt;&#34; urn:StatusCode Value =&#34; XML_Parameter2&#34; /&#34;&gt;&#34;     &#34;&LT;&#34; /瓮:状态&#34;&GT;&#34; &#34;&LT;&#34; /瓮:LogoutResponse&#34;&GT;&#34 ;;

XML值:#xmlns:urn = protocol - &gt;断言&amp; #xmlns:urn = protocol - &gt;状态:成功;

响应参数:RelayState&amp; SigAlg&amp; Signature;

签名类型:SHA256withRSA;

KeyStore类型:PKCS12;

KeyStore文件:D:\ saml_cert.pfx;

KeyStore密码:**********;

证书:{xxxxxxxxxx}

此定义中的键与值分开,使用&#34;:&#34;并且使用&#34;;&#34;指定值的结尾。 (不是新行)这允许根据用于SSO连接的相应IdP配置中的IdP服务提供商的要求设置SAML响应的完全参数化。 定义如下:

•SLO响应:这是相对地址,必须在相应的IdP服务器上发送SLO响应。

•SLO响应XML:这是定义以XML格式构建的SLO响应的文本字符串(使用&#34;&lt;&#34;和&#34;&gt;&#34;没有&#34;) 。识别参数图中找到的参数的字符串被交换为它们各自的值。为了确保正确识别类似参数,Cookie参数具有前导&#34; $&#34;并且请求查询的XML参数是一个前导&#34;#&#34;。另外还提​​供了2个公式,其中&#34; @UUID&#34;将使用XML响应的ID参数的正确格式计算随机UUID,并且&#34; @ ACTUAL_TIME&#34;将使用Instant Response格式为XML Response的IssueInstant参数计算正确的时间戳。

•XML值:此文本字符串标识其他参数,其中基本上使用了已知参数,但需要交换参数值的一部分以匹配所需文本。参数由字符串&#34; XML_Paramater&#34;标识。然后是字符串中的位置,用&#34;&amp;&#34;分隔每个值。在SLO响应XML文本中。 XML值的文本是通过参数标识后跟&#34; =&#34;来构建的。和要替换的文本后跟&#34; - &GT; &#34;和新的文本。

•响应参数:响应参数用&#34;&amp;&#34;分隔。并将按定义添加到SLO响应中。如果需要签名,则此字符串中需要参数SigAlg和Signature,并且应放在最后。

•签名类型:如果需要签名,则在此处指定用于计算签名的算法类型。

•KeyStore类型:这是用于证书的KeyStore的类型。

•KeyStore文件:这是保存KeyStore的文件,包括Lotus Notes服务器上的驱动器和路径。我们在测试服务器上使用了D:\ saml_cert.pfx。

•KeyStore密码:这是打开KeyStore文件及其中存储的证书所需的密码。

•证书:这是在KeyStore文件中标识证书的证书的别名。如果证书存储在新的KeyStore文件中以在一个位置组合多个证书,则别名总是更改为新值,必须在此处进行调整。

我实现的代码是一个名为&#34; Logout&#34;在domcfg.nsf中,它基本上可以在任何可用于SSO用户的数据库中实现,并且它作为服务器运行,以允许保护idpcat.nsf中具有最高安全性的IdP配置。在IdP服务提供程序上,您必须分别为Domino服务器配置SLO请求,并将相应的网站配置为&#34; https://WEBSITE/domcfg.nsf/Logout?Open&&#34;然后是SAML请求。如果IdP服务提供商请求签名,则必须存储带有证书的KeyStore文件,其中包括签名所需的PrivateKey。可以使用MMC管理单元功能管理KeyStore文件(请参阅https://msdn.microsoft.com/en-us/library/ms788967(v=vs.110).aspx)。可以通过导出功能将多个证书组合到一个文件中,但您必须确保通过导出向导中的相应设置将私钥导出到文件中。

这是&#34; Logout&#34;的代码。代理,从多米诺骨牌服务器注销用户并将SAML注销响应发送给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();
    }
}

正如您可能已经认识到的那样,如果定义不正确并且不能导致成功注销,则可以使用多个注释行来调试代理。您可以通过删除&#34; //&#34;轻松更改这些行。启动这些行并打印出您希望在屏幕上看到的参数或将它们发送到日志。

要在多米诺骨牌服务器上启动SLO,我使用相同的概念编写了另一个Java代理。该代理程序名为startSLO,位于与&#34; Logout&#34;相同的数据库中。剂。通过创建打开相对URL&#34; /domcfg.nsf/startSLO?Open&#34;的按钮,可以在任何应用程序中轻松实现此代理的使用。 &#34; startSLO&#34; agent具有以下代码:

import lotus.domino.*;
import java.io.*;

public class JavaAgent extends AgentBase {
    public void NotesMain() {
        try {
            Session ASession = getSession();
            AgentContext AContext = ASession.getAgentContext();

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

            //Load Data from Logout Request
            Document Doc = AContext.getDocumentContext();
            String ServerName = Doc.getItemValueString("HTTP_HSP_HTTPS_HOST");
            int pos = ServerName.indexOf(":");
            ServerName = pos > 0 ? ServerName.substring(0, ServerName.indexOf(":")) : ServerName;
            String Query = Doc.getItemValueString("Query_String");
            pos = Query.indexOf("?Open&");
            Query = pos > 0 ? "?" + Query.substring(Query.indexOf("?Open") + 6) : "";
            Doc.recycle();
            DB.recycle();

            //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);
            String SAMLSLO = idpDoc.getItemValueString("SAMLSloUrl");
            idpDoc.recycle();
            idpView.recycle();
            idpcat.recycle();
            Dir.recycle();

            //Send Logout to Server and redirect to Response to defined Destination
            PrintWriter pwsaml = getAgentOutput();
            pwsaml.flush();
            pwsaml.println("[" + SAMLSLO + Query + "]");
            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();
        } 
    }
}