zoukankan      html  css  js  c++  java
  • 关于WEB Service&WCF&WebApi实现身份验证之WCF篇(2)

    因前段时间工作变动(换了新工作)及工作较忙暂时中断了该系列文章,今天难得有点空闲时间,就继续总结WCF身份验证的其它方法。前面总结了三种方法(详见:关于WEB Service&WCF&WebApi实现身份验证之WCF篇(1)),今天又将分享三种方法,完成WCF篇。

    第四种:SOAP Header验证

    首先定义一个WCF服务契约及服务实现类(后面的各种验证均采用该WCF服务),我这里直接采用默认的代码,如下:

    服务契约定义:

    namespace WcfService1
    {
        // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。
        [ServiceContract(Namespace="http://www.zuowenjun.cn")]
        public interface IService1
        {
    
            [OperationContract]
            string GetData(int value);
    
            [OperationContract]
            CompositeType GetDataUsingDataContract(CompositeType composite);
    
            // TODO: 在此添加您的服务操作
        }
    
    
        // 使用下面示例中说明的数据约定将复合类型添加到服务操作。
        [DataContract]
        public class CompositeType
        {
            bool boolValue = true;
            string stringValue = "Hello ";
    
            [DataMember]
            public bool BoolValue
            {
                get { return boolValue; }
                set { boolValue = value; }
            }
    
            [DataMember]
            public string StringValue
            {
                get { return stringValue; }
                set { stringValue = value; }
            }
        }
    }
    

    服务实现Service1.svc:

    namespace WcfService1
    {
        public class Service1 : IService1
        {
            public string GetData(int value)
            {
                return string.Format("You entered: {0}", value);
            }
    
            public CompositeType GetDataUsingDataContract(CompositeType composite)
            {
                if (composite == null)
                {
                    throw new ArgumentNullException("composite");
                }
                if (composite.BoolValue)
                {
                    composite.StringValue += "Suffix";
                }
                return composite;
            }
        }
    }
    

     然后就开始编写实现SOAP Header验证的具体逻辑,为了便于扩展及可移植性,我这里新建一个类库项目:WcfServiceBehaviorExtension,在该项目中定义三个类,第一个是实现自IClientMessageInspector的类:AttachUserNamePasswordInspector,用于实现客户端在调用WCF服务时自动附加用户信息(用户名及密码);第二个是实现自IDispatchMessageInspector, IEndpointBehavior的类:ValidateUserNamePasswordBehavior,用于实现自定义消息分发及验证用户信息;第三个是继承自BehaviorExtensionElement的类:ValidateUserNamePasswordElement,用于实现endpointBehavior扩展,可直接配置在config文件中,若采用在代码动态添加ValidateUserNamePasswordBehavior则这类可以不用定义,下面依次分享这三个类的实现代码。

    AttachUserNamePasswordInspector

    namespace WcfServiceBehaviorExtension
    {
        public class AttachUserNamePasswordInspector : IClientMessageInspector
        {
            private static string userName = System.Configuration.ConfigurationManager.AppSettings["username"];
            private static string password = System.Configuration.ConfigurationManager.AppSettings["pwd"];
    
            public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
            {
                
            }
    
            public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
            {
                MessageHeader userNameHeader = MessageHeader.CreateHeader("OperationUserName", "http://www.zuowenjun.cn", userName, false, "");
                MessageHeader pwdNameHeader = MessageHeader.CreateHeader("OperationPwd", "http://www.zuowenjun.cn", password, false, "");
    
                request.Headers.Add(userNameHeader);
                request.Headers.Add(pwdNameHeader);
                return null;
            }
        }
    }
    

    ValidateUserNamePasswordBehavior

    namespace WcfServiceBehaviorExtension
    {
    
        public class ValidateUserNamePasswordBehavior : IDispatchMessageInspector, IEndpointBehavior
        {
    
            #region IDispatchMessageInspector 成员
    
    
            private string GetHeaderValue(string key)
            {
    
                int index = OperationContext.Current.IncomingMessageHeaders.FindHeader(key, "http://www.zuowenjun.cn");
    
                if (index >= 0)
                {
                    return OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(index).ToString();
                }
    
                return null;
            }
    
    
    
            public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
            {
                string username = GetHeaderValue("OperationUserName");
                string pwd = GetHeaderValue("OperationPwd");
    
                if (!(username == "admin" && pwd == "wcf.admin"))
                {
                    throw new FaultException("操作的用户名或密码不正确!");
                }
                return null;
    
            }
    
    
    
            public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
            {
    
            }
    
    
    
            #endregion
    
    
    
            #region IEndpointBehavior 成员
    
    
    
            public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
            {
    
            }
    
    
    
            public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
            {
                clientRuntime.MessageInspectors.Add(new AttachUserNamePasswordInspector());
            }
    
    
    
            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
            {
                endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ValidateUserNamePasswordBehavior());
            }
    
    
    
            public void Validate(ServiceEndpoint endpoint)
            {
            }
    
    
    
            #endregion
    
        }
    
    }
    

    ValidateUserNamePasswordElement

    namespace WcfServiceBehaviorExtension
    {
        public class ValidateUserNamePasswordElement: BehaviorExtensionElement
        {
            public override Type BehaviorType
            {
                get { return typeof(ValidateUserNamePasswordBehavior); }
            }
    
            protected override object CreateBehavior()
            {
                return new ValidateUserNamePasswordBehavior();
            }
        }
    }
    

    定义好上述类后,我们再在WCF服务项目:WcfService1中引用该项目,然后修改配置文件web.config:

      <system.serviceModel>
        <extensions>
          <behaviorExtensions>
            <add name="soapHaderValidation" type="WcfServiceBehaviorExtension.ValidateUserNamePasswordElement,WcfServiceBehaviorExtension"/>
          </behaviorExtensions>
        </extensions>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
          <endpointBehaviors>
            <behavior name="service1behavior">
              <soapHaderValidation />
            </behavior>
          </endpointBehaviors>
        </behaviors> 
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
        <services>
          <service name="WcfService1.Service1">
            <endpoint binding="wsHttpBinding" contract="WcfService1.IService1" behaviorConfiguration="service1behavior"></endpoint>
          </service>
        </services>
      </system.serviceModel>
    

    注意以下几点:

    1.配置extensions > behaviorExtensions节点,里面添加ValidateUserNamePasswordElement类型信息,name为自定义名称,type为ValidateUserNamePasswordElement的完整类型名称,中间加逗号分隔,后面是该类所在的程序集名称;

    2.配置endpointBehaviors > behavior节点,增加1中命名的节点soapHaderValidation,这里可能会报错误,但请不要理会,因为该节点在运行时才会生效的。

    3.配置endpoint节点,需要添加behaviorConfiguration属性,并指定到2中所相对应的behavior

    上述配置说白了就是配置自定义的服务行为类,使用在运行时注入到WCF服务管道中。

     服务端设置好后并发布运行,最后就是客户端引用该WCF服务,同时需要引用WcfServiceBehaviorExtension项目,然后修改配置文件app.config(我这里演示采用的是控制台程序):

        <system.serviceModel>
          <extensions>
            <behaviorExtensions>
              <add name="soapHaderValidation" type="WcfServiceBehaviorExtension.ValidateUserNamePasswordElement,WcfServiceBehaviorExtension"/>
            </behaviorExtensions>
          </extensions>
          <behaviors>
            <endpointBehaviors>
              <behavior name="service1behavior">
                <soapHaderValidation />
              </behavior>
            </endpointBehaviors>
          </behaviors>
            <bindings>
                <wsHttpBinding>
                    <binding name="WSHttpBinding_IService1" />
                </wsHttpBinding>
            </bindings>
            <client>
                <endpoint address="http://localhost:7704/Service1.svc" binding="wsHttpBinding"
                    bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference.IService1"
                    name="WSHttpBinding_IService1" behaviorConfiguration="service1behavior">
                </endpoint>
            </client>
        </system.serviceModel>
    

    注意事项与服务端配置文件相同,即需要配置extensions > behaviorExtensions节点、endpointBehaviors > behavior节点、endpoint节点添加behaviorConfiguration属性

    另还需在appSettings节点中增加配置用户名及密码

    配置好后就可以在程序中调用WCF服务,调用方法过程与普通的调用方法相同,并无其它区别。当然如果不想在客户端配置自定义节点及,可以通过以下代码来动态的添加用户名及密码信息:

            static void Main(string[] args)
            {
    
                try
                {
                    var client = new ServiceReference.Service1Client();
                    using (OperationContextScope contextScope = new OperationContextScope(client.InnerChannel))
                    {
                        AppendUserInfoHeader("admin", "wcf.admin");
                        string str = client.GetData(1);
                        Console.WriteLine(str);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("ERROR:{0}", ex.Message);
                }
    
                Console.WriteLine("我是主线程!");
                Console.Read();
            }
    
    
            static void AppendUserInfoHeader(string username, string pwd)
            {
                MessageHeader header = MessageHeader.CreateHeader("OperationUserName", "http://www.zuowenjun.cn", username);
                OperationContext.Current.OutgoingMessageHeaders.Add(header);
    
                header = MessageHeader.CreateHeader("OperationPwd", "http://www.zuowenjun.cn", pwd);
                OperationContext.Current.OutgoingMessageHeaders.Add(header);
    
            }
    

    第五种:集成Windows用户组授权与认证

    首先,在实现服务契约时,我们在需要授权与认证的类或方法中添加PrincipalPermission特性,代码如下:

            [PrincipalPermission(SecurityAction.Demand,Role="Users")]
            public string GetData(int value)
            {
                return string.Format("You entered: {0}", value);
            }
    

    这里我配置的是只有当请求的WINDOWS凭证的用户角色类型为Users才允许调用该方法,可以设置成其它角色或指定用户名(Name)等。

    然后修改配置文件,配置如下:

      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior name="service1behavior">
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
              <serviceAuthorization principalPermissionMode="UseWindowsGroups"></serviceAuthorization>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
        <services>
          <service name="WcfService1.Service1" behaviorConfiguration="service1behavior">
            <endpoint name="service1" binding="wsHttpBinding" contract="WcfService1.IService1" ></endpoint>
          </service>
        </services>
      </system.serviceModel>
    

    这里需要注意,需配置serviceBehaviors > behavior > serviceAuthorization节点,将principalPermissionMode属性设为:UseWindowsGroups,并同时在service节点上指定behaviorConfiguration即可。

    最后客户端正常引用该WCF服务,然后在调用WCF服务时,需要创建WindowsClientCredential对象,并指定用户名与密码,这里的用户名与密码均为该WCF服务端上服务器上的WINDOWS用户名及密码,具体代码如下:

            static void Main(string[] args)
            {
    
                try
                {
                    var client = new ServiceReference.Service1Client();
                    NetworkCredential credential = client.ChannelFactory.Credentials.Windows.ClientCredential;
                    credential.UserName = "test";
                    credential.Password = "test";
    
                    string str = client.GetData(1);
                    Console.WriteLine(str); ;
    
                }
                catch (Exception ex)
                {
                    Console.WriteLine("ERROR:{0}", ex.Message);
                }
    
                Console.WriteLine("我是主线程!");
                Console.Read();
            }
    

    第六种:集成Windows验证

    服务定义与实现与文章开头贴出的代码相同,无任何特殊的地方,唯一的区别在于配置文件,如下:

      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <basicHttpBinding>
            <binding name="service1binding">
              <security mode="TransportCredentialOnly">
                <transport clientCredentialType="Windows"></transport>
              </security>
            </binding>
          </basicHttpBinding>
        </bindings>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
        <services>
          <service name="WcfService1.Service1" >
            <endpoint name="service1" binding="basicHttpBinding" bindingConfiguration="service1binding" contract="WcfService1.IService1" ></endpoint>
          </service>
        </services>
      </system.serviceModel>
    

    注意事项:

    1.绑定类型,我这里采用basicHttpBinding(不是每个绑定都支持WINDOWS验证的),同时需配置该绑定类型的security节点,mode设为TransportCredentialOnly,transport.clientCredentialType设为Windows。

    完成上述配置后再直接访问WCF服务地址时,弹出需要输入用户名及密码,就表明WINDOWS验证已生效,接下来就是客户端引用该WCF服务,引用后配置文件自动进行了相应的更新,这里我们无需修改,然后在调用WCF服务时需要传入WindowsClientCredential对象,并指定用户名与密码,这里的用户名与密码均为该WCF服务端上服务器上的WINDOWS用户名及密码,与第五种相同代码就不贴出来了。

  • 相关阅读:
    python基础——列表生成式
    python基础——迭代
    python基础——切片
    python基础——递归函数
    python基础——函数的参数
    python基础——使用dict和set
    python基础——使用list和tuple
    python基础——字符串和编码
    堆——神奇的优先队列(下)
    堆——神奇的优先队列(上)
  • 原文地址:https://www.cnblogs.com/zuowj/p/5078500.html
Copyright © 2011-2022 走看看