如何为使用Unity InjectionFactory创建的WCF通道引导securitytoken

时间:2014-02-11 04:46:28

标签: wcf federated-identity sts-securitytokenservice thinktecture-ident-server

我在处理以下情况时遇到了相当大的挑战。

  1. 我想在需要时使用Unity DI Framework为我的服务创建一个新频道。
  2. 该服务受联邦安全保护。
  3. 该服务不是从IIS内托管的服务中调用,而是从自托管WCF服务中调用。
  4. 我目前的问题发生在上面的第3步 - 如何引导令牌然后满足上述2个要求?我将概述每个步骤的解决方案,因为它们非常重要,并希望能帮助其他人解决这个问题。

    解决问题1: 以下代码片段将在需要时随时创建新的通道实例。

    container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
        new InjectionFactory(
            x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
                .CreateChannel()));
    

    以下是一些人比我更好的例子: http://unity.codeplex.com/discussions/211736 https://gist.github.com/tracker1/5675161

    你也可以使用替代终身经理,例如TransientLifetimeManager在这里运作良好。

    解决问题2: 现在真正的困难开始了 - 如何在InjectionFactory中包含安全令牌? 显然我想要使用CreatChannelWithIssuedToken,但是我需要抓住引导令牌才能这样做。这在网络周围有很好的记录,例如:这里: http://www.cloudidentity.com/blog/2012/11/30/using-the-bootstrapcontext-property-in-net-4-5-2/ 需要注意的一些重要事项:确保配置中有serviceBehaviors部分指定useIdentityConfiguration =“true”,否则将忽略system.identityModel部分,例如。

      <serviceBehaviors>
        <behavior name="">
          <serviceCredentials useIdentityConfiguration="true" />
        </behavior>
      </serviceBehaviors>
    

    然后你的system.identityModel部分也应该有:

    <system.identityModel>
      <identityConfiguration>
        <securityTokenHandlers>
          <securityTokenHandlerConfiguration saveBootstrapContext="true" />
        </securityTokenHandlers>
      </identityConfiguration>
    </system.identityModel>
    

    然后,如果您已经请求在会话上正确设置此项(请参阅我的另一个问题:AJAX call against REST endpoint secured with Thinktecture's IdentityServer STS,只要您访问引导上下文安全令牌,它就会在会话中可用: / p>

    var bootstrapContext = ((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext 
        as BootstrapContext;
    SecurityToken securityToken = bootstrapContext.SecurityToken;
    

    然后,您可以将InjectionFactory更改为如下所示:

    container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
        new InjectionFactory(
            x =>
            {
                var bootstrapContext = ((ClaimsIdentity)
                    Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
                var securityToken = bootstrapContext.SecurityToken;
                return new  ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
                    .CreateChannelWithIssuedToken(securityToken);
            }));
    

    或者更好的是,创建一个继承自ChannelFactory的类,并添加一个将上述逻辑组合成一个CreateChannelWithIssuedTokenUsingBootstrapContext方法的方法:

    public class ChannelFactoryWithChannelFactoryOperations<T> : ChannelFactory<T>
    {
        protected ChannelFactoryWithChannelFactoryOperations(Type channelType) : base(channelType)
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations()
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName)
            : base(endpointConfigurationName)
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName,
            EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress)
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations(Binding binding) : base(binding)
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations(Binding binding, string remoteAddress)
            : base(binding, remoteAddress)
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations(Binding binding, EndpointAddress remoteAddress)
            : base(binding, remoteAddress)
        {
        }
    
        public ChannelFactoryWithChannelFactoryOperations(ServiceEndpoint endpoint) : base(endpoint)
        {
        }
    
        public T CreateChannelWithIssuedTokenUsingBootstrapContext()
        {
            var bootstrapContext =
                ((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
            SecurityToken securityToken = bootstrapContext.SecurityToken;
            return CreateChannelWithIssuedToken(securityToken);
        }
    }
    

    这样你就可以调用它:

    container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
        new InjectionFactory(
        x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
                        .CreateChannelWithIssuedTokenUsingBootstrapContext()));
    

    解决问题3: 除了上述两个问题的复杂性之外,我现在在我自己的自托管WCF服务中尝试IIS之外的同样的事情。同样的服务完全是无国籍的,所以这就是我的下一个困境: 安全令牌的引导仍然会发生,但它不会发生在正确的线程上。 Unity似乎在一个单独的线程中运行它的InjectionFactory到实际的服务调用执行。

    即。当上面的InjectionFactory委托执行时,CurrentPrincipal是未经授权的GenericPrincipal。这与我们在上面的问题2中所做的不同 - 它是一个授权的ClaimsPrincipal - 我相信这都是由IIS会话设置的(如果我不正确,请随时纠正)。

    有趣的是,如果我们用

    替换上面的内容
    container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
        new InjectionFactory(
        x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
                        .CreateChannel()));
    

    即。现在只注入一个不安全的Channel对象,我们可以观察到在我们自己托管的WCF服务中,我们实际上尝试与通道进行交互,Thread.CurrentPrincipal是经过身份验证的ClaimsPrincipal,SecurityToken正确地在主体上进行Bootstrap。

    所以问题可归纳如下: 因为InjectionFactory委托在尚未进行身份验证/授权的线程上执行,所以SecurityToken实际上无法传递给通道的创建。

    有人对我如何解决这个问题有任何建议吗?我是否已经将自己与自我托管的WCF和团结的特殊组合描绘成一个角落?

    谢谢, 奇

1 个答案:

答案 0 :(得分:1)

我找到了一种方法来做到这一点,虽然我喜欢更好的建议。 SessionSecurityTokenHandler ValidateToken方法总是在与InjectionFactory委托相同的Thread上执行,因此我们可以在CustomSessionSecurityTokenHandler的ValidateToken方法中设置Thread的CurrentPrincipal,如下所示:

public class CustomSessionSecurityTokenHandler: SessionSecurityTokenHandler
{
    public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
    {
        var claimsIdentities = base.ValidateToken(token);

        Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentities);

        return claimsIdentities;
    }
}

然后需要修改system.identityModel配置以包含自定义securityTokenHandler:

  <securityTokenHandlers>
    <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    <add type="MyAssembly.CustomSessionSecurityTokenHandler, MyAssembly" />
  </securityTokenHandlers>

完成后,尝试从Bootstrap上下文访问安全令牌然后成功:

container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
    new InjectionFactory(
        x =>
        {
            var bootstrapContext = ((ClaimsIdentity)
                 Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
            var securityToken = bootstrapContext.SecurityToken;
            return new  ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
                 .CreateChannelWithIssuedToken(securityToken);
        }));

请随意添加任何其他建议! :)

谢谢, 奇