前面写了 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

posted on 2016-09-06 04:53  zhouandke  阅读(2239)  评论(0编辑  收藏  举报