在面向服务的解决方案中有效地使用 SSO

面向服务的解决方案使用企业单一登录 (SSO) 来存储配置值和处理后端系统的凭据。 为缩短延迟时间,该解决方案使用本地缓存来存储配置值。 该解决方案每隔五分钟刷新一次缓存。

在许多应用程序中,都使用适配器来处理 SSO 操作,包括获取 SSO 票证,兑换票证,以及使用凭据来获取对关联应用程序的访问。 但是,面向服务的解决方案的内联版本不使用适配器。 它必须通过代码使用 SSO。

本主题介绍了该解决方案所使用的缓存机制,以及该解决方案的内联版本如何通过代码使用 SSO。

配置值的本地缓存

面向服务的解决方案使用两个对象 (ConfigPropertyBagConfigParameters)来处理配置值。 ConfigPropertyBag 类保存值,并且仅由 ConfigParameters 类使用。 解决方案的其他部分使用 ConfigParameters 类来检索配置参数。 这两个类都位于 Microsoft.Samples.BizTalk.WoodgroveBank.ConfigHelper 命名空间中。

注意

面向服务的解决方案将缓存刷新间隔固定为 300 秒(5 分钟)。 相反,你可能希望使缓存刷新间隔本身成为此解决方案中的可配置属性。 在业务流程管理解决方案中就是这么做的。 有关该解决方案如何处理 SSO 的详细信息,请参阅 在业务流程管理解决方案中高效使用 SSO。 请注意,在这种情况下,对刷新间隔所做的更改直到在原有间隔结束时发生的刷新缓存之后才会生效。

ConfigPropertyBag 具有以下方法:

方法 说明
读取 检索给定属性的值。
写入 为属性赋值。

类使用 .NET NameValueCollection 的实例来保存值。 这两种访问方法从 Microsoft.BizTalk.SSOClient.Interop 命名空间实现 IPropertyBag 接口。 ConfigPropertyBag 类是内部类,仅由 ConfigParameters 类使用。

注意

属性包中的键名不区分大小写。 基础 NameValueCollection 使用不区分大小写的哈希和不区分大小写的比较。

应用程序使用 ConfigParameters 类来处理 SSO 配置值。 这个类具有以下公共方法和属性:

方法或属性 说明
SSOConfigParameter 用于指定配置参数的枚举。
GetConfigParameters 用于检索给定参数值的方法。 使用 SSOConfigParameter 来指示该参数。

ConfigParameters 类使用 .NET Timer 类和委托函数来设置 ConfigPropertyBag 的刷新:

private static Timer cacheRefreshTimer;  
private static ISSOConfigStore ssoConfigStore;  
private static ReaderWriterLock syncLock;  
  
// Cache refresh interval in milliseconds  
private const int CacheRefreshInterval = 5 * 60 * 1000;  
private static ConfigPropertyBag ssoPropBag;  
  
static ConfigParameters()  
{  
    ssoConfigStore = new ISSOConfigStore();  
    ssoPropBag = new ConfigPropertyBag();  
    syncLock = new ReaderWriterLock();  
  
    ssoConfigStore.GetConfigInfo(SSO_CONFIG_APP,  
         SSO_IDENTIFIER_NAME, SSOFlag.SSO_FLAG_RUNTIME,  
         ssoPropBag);  
  
    cacheRefreshTimer = new Timer(  
        new TimerCallback(ConfigParameters.cacheRefreshCallback),  
        null, CacheRefreshInterval, CacheRefreshInterval);  
}  

请注意,静态构造函数初始化的是静态成员变量,因此即使不创建类实例也可以使用类方法。 构造函数创建 SSO 配置存储的实例 (ISSOConfigStore) 、配置属性包 (ConfigPropertyBag) ,以及用于在更新和读取期间控制对 ConfigurationPropertyBag 的访问 (ReaderWriterLock) 同步锁。 然后,构造函数使用 GetConfigInfo 检索 SSO 配置值并将其放入属性包中。 最后,构造函数创建一个 Timer 对象,该对象在指定的间隔后调用委托函数 cacheRefreshCallback

Timer 委托函数相对简单:

private static void cacheRefreshCallback(object state)  
{  
    // Disable the timer until we are done loading the cache.  
    cacheRefreshTimer.Change(Timeout.Infinite, CacheRefreshInterval);  
  
    // Put the data from SSO in a new property bag so that  
    // we don't have to lock the property bag and block it from being  
    // used. The SSO call is a remote call and may take a while.  
    ConfigPropertyBag propBag2 = new ConfigPropertyBag();  
    ssoConfigStore.GetConfigInfo(SSO_CONFIG_APP,   
        SSO_IDENTIFIER_NAME, SSOFlag.SSO_FLAG_RUNTIME, propBag2);  
  
    // Get a writer lock before updating the cached values.  
    syncLock.AcquireWriterLock(Timeout.Infinite);  
  
    try  
    {  
        ssoPropBag = propBag2;  
    }  
    finally   
    {  
        syncLock.ReleaseWriterLock();  
    }  
    // Enable the timer.  
    cacheRefreshTimer.Change(CacheRefreshInterval,   
        CacheRefreshInterval);  
}  

请注意,cacheRefreshCallback 方法会禁用该计时器,以便该方法可以全程运行。 还要注意在控制对属性包的访问时锁的使用。 ReaderWriterLock 是此处的最佳选择,它专为读取多于写入的情况而设计。 另请注意,lock syncLock 是静态的,在类级别声明,即所有线程共享它的同一个实例。

通过代码使用 SSO

从代码使用单一登录时,代码必须承担适配器的角色:即,从消息中检索 SSO 票证,兑换票证以获取后端系统的用户名和密码,最后使用后端系统。 面向服务的解决方案通过 PendingTransactionsCaller 对象的 GetPendingTransactionsResponse 方法执行此操作。

该方法如下所示:

public static PendingTransactionsResponse GetPendingTransactionsResponse(XLANGMessage requestMsg)  
{  
    try  
    {  
        // Get config parameter values.  
        int ptTimeout = Convert.ToInt32(  
            ConfigParameters.GetConfigParameter(  
                ConfigParameters.  
                    SSOConfigParameter.  
                        PENDING_TRANSACTIONS_INLINE_TIMEOUT  
            )  
        );  
  
        string ptURL = ConfigParameters.GetConfigParameter(  
            ConfigParameters.  
                SSOConfigParameter.  
                    PENDING_TRANSACTIONS_URL  
        );  
        string ssoAffliateApp = ConfigParameters.  
            GetConfigParameter(ConfigParameters.  
                SSOConfigParameter.  
                    PENDING_TRANSACTIONS_SSO_AFFILIATE_APP  
            );  
  
        // Redeem the SSO ticket and get the userid/password to   
        // use to interact with Pending Transaction System.  
  
        // Extract the ticket…  
        string msgTicket = (string)requestMsg.  
            GetPropertyValue(typeof(BTS.SSOTicket));  
  
        // and the user name of the originating user.  
        string originatorSID = (string)requestMsg.  
            GetPropertyValue(  
                typeof(  
                    Microsoft.BizTalk.XLANGs.BTXEngine.OriginatorSID  
                )  
            );  
  
        string pendTransUserName;  
        // Now, redeem the ticket.  
        string[] pendTransCredential =   
            ssoTicket.RedeemTicket(  
                ssoAffliateApp,  
                originatorSID,  
                msgTicket,  
                SSOFlag.SSO_FLAG_NONE,  
                out pendTransUserName  
            );   
  
        PendingTransactionsRequest req =   
            (PendingTransactionsRequest)requestMsg[0].  
                RetrieveAs(  
                    typeof(PendingTransactionsRequest)  
                );  
        PendingTransactionsResponse resp;  
  
        using (PendingTransactionsWebService  
            svc = new PendingTransactionsWebService())  
        {  
            svc.Url = ptURL;  
            svc.Timeout = ptTimeout;  
  
            // The web service uses basic authentication, so we  
            //need to send the user id and password in the request.  
            CredentialCache credCache = new CredentialCache();  
            NetworkCredential credentialToUse =  
                new NetworkCredential(  
                    pendTransUserName, pendTransCredential[0]  
                );  
            credCache.Add(new Uri(svc.Url), "Basic", credentialToUse);  
            svc.Credentials = credCache;  
  
            resp = svc.GetPendingTransactions(req);  
        }  
        return resp;                  
    }  
    catch (System.Net.WebException webEx)  
    {  
        if (webEx.Status == WebExceptionStatus.Timeout)  
        {  
            throw new PendingTransactionsTimeoutException();  
        }  
        else  
        {  
            Trace.WriteLine("Other Net.WebException: "  
                + webEx.ToString()   
                + (null == webEx.InnerException ? "" :  
                     ("Inner Exception: "   
                        + webEx.InnerException.ToString())  
                )  
            );  
            throw;  
        }  
    }  
    catch(System.Exception ex)  
    {  
        Trace.WriteLine("Other Exception: "  
            + ex.ToString()   
            + (null == ex.InnerException ? "" :   
                 ("Inner Exception: "  
                    + ex.InnerException.ToString())  
                  )  
            );  
        throw;  
    }  
}  

该方法首先检索后端系统的配置信息(包括 URL)以及后端(关联)应用程序的名称。

为了兑换票证,该方法必须从消息中提取票证和原始请求用户名。 消息包含作为消息上下文属性之一的票证 BTS。SSOTicket。 有关详细信息,请参阅 UI 指南和开发人员 API 命名空间参考中的消息上下文属性。 方法还从消息上下文属性中提取 OriginatorSID 。 有了票证和创建者名称后,该方法将对票证调用 RedeemTicket 方法以检索凭据。

其余的代码将为凭据创建一个 .NET NetworkCredential 缓存,并调用后端 Web Services。

注意

由于用户名和密码是以明文形式从 SSO 中返回的,因此应最大限度地缩短保存该信息的变量的生存期。 请注意,代码在 try 块中声明凭据变量。 此处,变量在从 try 块退出时过期。

另请参阅

面向服务的解决方案的实施要点