这个问题已解决,是绑定设置的问题,主要还是因为我自己没有深入理解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); }
这样,再次进行订单结账操作就能成功了。
这个问题到底是为什么呢。我真的百思不得其解啊。