【已解决】谁动了我的CurrentPrincipal?求助我在给Artech的wcf petshop增加授权机制的时候遇到的问题。

这个问题已解决,是绑定设置的问题,主要还是因为我自己没有深入理解WCF绑定的安全机制。在这篇博客里面我来说说怎么解决的。

下载了Artech的wcf petshop源码(博文链接)并调试运行成功后,打算在其之上增加授权功能,但是遇到了问题。

环境:windows8.1 企业版,VS2012, SQLExpress2008, IIS Express

首先我在原文中求助A大关于通过RoleManager获取角色是否可行,得到了A大的答复可行,于是我就在原来的项目的Common中增加了一个静态类用于获取角色并设置到当前线程的CurrentPrincipal,和还原CurrentPrincipal。代码如下:

 1 namespace Artech.PetShop.Common
 2 {
 3     /// <summary>
 4     /// 根据当前用户名设置当前线程的Principal的类
 5     /// </summary>
 6     public static class SetThreadPrincipal
 7     {
 8         /// <summary>
 9         /// 用于保存修改之前的Principal
10         /// </summary>
11         private static IPrincipal _oldPrincipal;
12 
13         /// <summary>
14         /// 设置当前线程的Principal,根据ApplicationContext里面的UserName是否存在来判断。
15         /// 通过Roles来获取对应UserName的角色。并将其设置到当前线程的CurrentPrincipal
16         /// </summary>
17         public static void SetThreadPrincipalToCurrentUser()
18         {
19             string username = ApplicationContext.Current.UserName;
20             if (!string.IsNullOrEmpty(username))
21             {
22                 // 如果用户名为空,则说明不可能有用户信息,那就不用去取角色并放到线程中。
23                 IIdentity identity;
24                 identity = new GenericIdentity(username, Membership.Provider.Name);
25 
26                 // TODO: Get Roles
27                 string[] roles = Roles.GetRolesForUser(identity.Name);
28                 IPrincipal principal = new GenericPrincipal(identity, roles);
29 
30                 // Place user's principal on the thread
31                 _oldPrincipal = Thread.CurrentPrincipal;
32                 Thread.CurrentPrincipal = principal;
33             }
34             else
35             {// 如果用户名为空,则说明不可能有用户信息,那就不用去取角色并放到线程中。
36                 // 这时候就不用设置oldPrincipal的值了。
37                 _oldPrincipal = null;
38             }
39         }
40 
41         /// <summary>
42         /// 恢复线程的Principal
43         /// </summary>
44         public static void ResumeThreadPrincipal()
45         {
46             if (_oldPrincipal != null)
47                 Thread.CurrentPrincipal = _oldPrincipal;
48         }
49     }
50 }

从代码中可以看到,如果取到上下文中的用户名之后就可以用户名对应的角色(关于Roles类的使用请微软搜索“角色提供”)。

然后我在ContextReceivalCallContextInitializer类的BeforeInvoke方法和AfterInvoke方法里面增加了对上面类的使用,代码如下:

ContextReceivalCallContextInitializer类在Infrastructure项目的WCF Extensions文件夹下。

 1     public class ContextReceivalCallContextInitializer : ICallContextInitializer
 2     {
 3         private IPrincipal _oldPrincipal;
 4         #region ICallContextInitializer Members
 5 
 6         public void AfterInvoke(object correlationState)
 7         {
 8             // 恢复Principal
 9             SetThreadPrincipal.ResumeThreadPrincipal();
10         }
11 
12         public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
13         {
14             ApplicationContext.Current = message.Headers.GetHeader<ApplicationContext>(
ApplicationContext.ContextHeaderLocalName,
ApplicationContext.ContextHeaderNamespace);
15 // 设置Principal 16 SetThreadPrincipal.SetThreadPrincipalToCurrentUser(); 17 return null; 18 } 19 20 #endregion 21 }

以上代码添加完成后,就可以见证奇迹了。点击启动调试(前提是已经配置成功可以运行)

登录、浏览pet信息、放入购物车略过,不用说了。看下面的图,已经放入购物车。

图:已经放入购物车

下一步,点击结账后,页面调用wcf,会进入ContextReceivalCallContextInitializer类的BeforeInvoke方法:

图:进入BeforeInvoke断点

按下F10,单步执行到return。

图:设置当前Principal结束

从监视窗口可以看到,线程的CurrentPrincipal已经成功设置。用户名和角色都OK的。

图:设置成功

这时,我们按下F5让程序继续进行,来到业务处理的断点处,奇迹就发生了:

图:来到业务处理

查看监视窗口,现成的当前Principal变了!

图:他竟然变成了我的windowsPrincipal!

然后继续执行,来到ContextReceivalCallContextInitializer的AfterInvoke方法时,又变回来了。

图:Principal回来了。

我反复尝试了多次都是这样!抓狂了。

如果我加上权限限制的代码,就会告诉我权限不对。

图:加上限制

再次执行就会在AuditCallHandler的Invoke方法里面看到异常:

图:异常

页面上也会告诉你,访问失败。

我无法理解这个问题到底是为什么,只好找个变通的方法来解决,把设置线程Principal的代码放到了OrderService里面:

        [OperationBehavior(TransactionScopeRequired= true)]
        [AuditCallHandler("提交订单")]
        public void Submit(Order order)
        {
            // 设置Principal   
            SetThreadPrincipal.SetThreadPrincipalToCurrentUser();
            this.BusinessComponent.Submit(order);
            // 恢复Principal
            SetThreadPrincipal.ResumeThreadPrincipal();
        }    

并将限制代码放到BusinessComponent的Submit里面:

        // 限制权限
        [PrincipalPermission(SecurityAction.Demand, Role = "User")]
        public void Submit(Order order)
        {
            this.ValidateInventory(order);
            this.DataAccess.Submit(order);
        }

这样,再次进行订单结账操作就能成功了。

这个问题到底是为什么呢。我真的百思不得其解啊。

原文地址:https://www.cnblogs.com/Arnu/p/PrincipalSetFail.html