zoukankan      html  css  js  c++  java
  • WCF权限控制

       前面写了 WCF账户密码认证, 实现了帐号密码认证, 接下来看看如何对方法的细粒度控制, 本文很大程度参考了 WCF安全之基于自定义声明授权策略, 这篇文章对原理讲得比较清楚, 而我这篇文章呢, 顶多算对操作实现进行补遗.

    自定义权限访问, 需要你实现两个类

    • 自定义授权策略声明集管理器:                找出某个用户的所有权限
    • 自定义的基于服务授权访问检查的管理器:    当前访问资源与权限集合比较, 并给出能否访问的结果

    1. 自定义授权策略声明集管理器

    需要注意的是需要始终允许Metadata请求(mex) https://msdn.microsoft.com/en-us/library/aa347849(v=vs.110).aspx

    using System;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.IdentityModel.Claims;
    using System.Security.Principal;
    
    namespace WCF_UserPassword
    {
        public class CustomServiceAuthorizationManager : ServiceAuthorizationManager
        {
            protected override bool CheckAccessCore(OperationContext operationContext)
            {
                //始终允许Metadata请求(mex)
                if (operationContext.EndpointDispatcher.ContractName == ServiceMetadataBehavior.MexContractName &&
                operationContext.EndpointDispatcher.ContractNamespace == "http://schemas.microsoft.com/2006/04/mex" &&
                operationContext.IncomingMessageHeaders.Action == "http://schemas.xmlsoap.org/ws/2004/09/transfer/Get")
                {
                    GenericIdentity identity = new GenericIdentity("");//必须插入一个Principal
                    operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = new GenericPrincipal(identity, null);
                    return true;
                }
    
                //访问的方法
                string action = operationContext.RequestContext.RequestMessage.Headers.Action;
                string userName = "";
                foreach (ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
                {
                    //找到用户名
                    foreach (Claim claim in cs.FindClaims(ClaimTypes.Name, Rights.PossessProperty))
                    {
                        userName = claim.Resource.ToString();
                    }
    
                    if (cs.Issuer == ClaimSet.System)//如果此声明是应用程序颁发的。
                    {
                        //参数ClaimType应该是ClaimTypes的公开成员,不过无所谓, 反正是字符串, 只要相同就可以了
                        var result = cs.FirstOrDefault(c => c.Resource.ToString() == action && c.ClaimType == "net.tcp://CustomClaimSet/" && c.Right == Rights.PossessProperty);
                        if (result != null)
                        {
                            //必须插入一个Principal
                            GenericIdentity identity = new GenericIdentity("");
                            operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = new GenericPrincipal(identity, null);
                            Console.WriteLine("同意{0}访问,URI:{1}", userName,action);
                            return true;
                        }
                    }
                }
                Console.WriteLine("拒绝访问,URI:{0}", action);
                Console.WriteLine();
                return false;
            }
        }
    }

    2. 自定义授权策略声明集管理器

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.IdentityModel.Claims;
    using System.IdentityModel.Policy;
    
    namespace WCF_UserPassword
    {
        class CustomAuthorizationPolicy : IAuthorizationPolicy
        {
            string id = string.Empty;
            public CustomAuthorizationPolicy()
            {
                id = new Guid().ToString();//每个声明集都是一个唯一的
            }
    
            //评估用户是否符合基于此授权策略的声明
            public bool Evaluate(EvaluationContext evaluationContext, ref object state)
            {
                bool flag = false;
                bool r_state = false;
                if (state == null) { state = r_state; } else { r_state = Convert.ToBoolean(state); }
                if (!r_state)
                {
                    IList<Claim> claims = new List<Claim>();//实体声明集
                    foreach (ClaimSet cs in evaluationContext.ClaimSets)
                    {
                        foreach (Claim claim in cs.FindClaims(ClaimTypes.Name, Rights.PossessProperty))
                        {
                            var userName = claim.Resource.ToString();
                            claims = claims.Concat(GetOperatorClaims(userName, "net.tcp://CustomClaimSet/", Rights.PossessProperty)).ToList();
                        }
                    }
                    evaluationContext.AddClaimSet(this, new DefaultClaimSet(Issuer, claims)); r_state = true; flag = true;
                }
                else { flag = true; }
                return flag;
            }
    
            // 赋予用户声明权限, 可以用数据库的方式
            private IList<Claim> GetOperatorClaims(string userName, string claimType, string right)
            {
                IList<Claim> claimList = new List<Claim>();
                if (userName == "admin")
                {
                    //第一个参数claimType应该是ClaimTypes的公开成员, 这个程序里最好用ClaimTypes.AuthorizationDecision, 不过无所谓, 反正是字符串, 只要相同就可以了
                    claimList.Add(new Claim(claimType, "http://tempuri.org/IService1/GetData", right));
                }
                //else if (userName == "admin2")  //作为测试, 这里没有给admin2对GetData方法的访问权限
                //{
                //    claimList.Add(new Claim(claimType, "http://tempuri.org/IService1/GetData", right));
                //}
                return claimList;
            }
    
            #region IAuthorizationComponent 成员/属性实现
            public ClaimSet Issuer
            {
                //ClaimSet.System表示应用程序可信颁发者的 System.IdentityModel.Claims.ClaimSet 对象
                get { return ClaimSet.System; }
                //另一种是ClaimSet.Windows 一组包含 Windows 安全标识符的声明, 用于Windows策略验证, 不适合这里
            }
            public string Id
            {
                get { return id; }
            }
            #endregion
        }
    }

    这段代码花花绿绿一大片, 其实也是从微软论坛提供的代码(找不到原文地址了), 涉及的知识比较多了, 如果对claim、Principal不太熟悉, 建议看看蒋金楠的《WCF技术剖析》下册 第七章, 弄懂背后的原理, 比实现代码有意义的多

    3. 下面是WCF配置操作

    进行下面的操作前, 请先编译或者运行一下, 因为上面添加的两个类需要被引用

    在服务行为中增加serviceAuthorization

    image

    image

    将principalPermissionMode 改为 Custom

    image

    修改serviceAuthorizationManagerType 改成 WCF_UserPassword.CustomServiceAuthorizationManager, WCF_UserPassword

    弹出的对话框中, 选择 bin –> debug

    image

    继续点进去, 你将看到编译成功的 CustomServiceAuthorizationManager

    image

    选中它, WCF服务配置器变成了这个样子

    image

    然后切换到授权策略, 添加授权策略

    image

    如同CustomServiceAuthorizationManager一样, 选择CustomAuthorizationPolicy

    image

    操作到这里, 记得要保存

    其实直接复制App.config 更方便, 你只需修改里面部分字符串即可(下面的是VS2013的, 比起VS2010, 要清爽很多)

    <serviceBehaviors>
      <behavior name="ServiceBehaviorToUserPassword">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
        <serviceCredentials>
          <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" />
          <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCF_UserPassword.MyCustomValidator,WCF_UserPassword" />
        </serviceCredentials>
        <!-- 以下部分请复制-->
          <serviceAuthorization principalPermissionMode="Custom" serviceAuthorizationManagerType="WCF_UserPassword.CustomServiceAuthorizationManager,WCF_UserPassword">
          <authorizationPolicies>
            <add policyType="WCF_UserPassword.CustomAuthorizationPolicy, WCF_UserPassword, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          </authorizationPolicies>
        </serviceAuthorization>
        <!--复制到这里-->
      </behavior>
    </serviceBehaviors>

    4. 测试

    客户端

            static void Main(string[] args)
            {
                var proxy = new ServiceReference1.Service1Client();
                Console.WriteLine("现在是admin访问");
                proxy.ClientCredentials.UserName.UserName = "admin";
                proxy.ClientCredentials.UserName.Password = "admin";
                try
                {
                    Console.WriteLine(proxy.GetData(2));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                Console.WriteLine();
    
                proxy = new ServiceReference1.Service1Client();
                Console.WriteLine("现在是admin2访问");
                proxy.ClientCredentials.UserName.UserName = "admin2";
                proxy.ClientCredentials.UserName.Password = "admin2";
                try
                {
                    Console.WriteLine(proxy.GetData(2));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }

    结果

    image

    源代码

    ok

  • 相关阅读:
    MVC根据角色自动选择母版页
    Redis学习笔记~五大数据结果的测试
    Redis学习笔记~Redis提供的五种数据结构
    将不确定变为确定~一切归总为“二”(C#中的位运算有啥用)
    Redis学习笔记~把redis放在DATA层,作为一种数据源,我认为更合理,也更符合我的面向对象原则
    屌丝程序员的那些事(一)毕业那年
    jquery的Flexigrid改造,支持选中行内容获取,两个表格行相互移动,行选中事件,支持dwr
    屌丝程序员的那些事(三)一起培训的那些人
    Centos 64位下搭建android开发环境需要的lib包
    屌丝程序员的那些事(二)第一次面试
  • 原文地址:https://www.cnblogs.com/zhouandke/p/5844300.html
Copyright © 2011-2022 走看看