这个问题已解决,是绑定设置的问题,主要还是因为我自己没有深入理解WCF绑定的安全机制。在这篇博客里面我来说说怎么解决的。
下载了Artech的wcf petshop源码(博文链接)并调试运行成功后,打算在其之上增加授权功能,但是遇到了问题。
环境:windows8.1 企业版,VS2012, SQLExpress2008, IIS Express
首先我在原文中求助A大关于通过RoleManager获取角色是否可行,得到了A大的答复可行,于是我就在原来的项目的Common中增加了一个静态类用于获取角色并设置到当前线程的CurrentPrincipal,和还原CurrentPrincipal。代码如下:
namespace Artech.PetShop.Common
{
/// <summary>
/// 根据当前用户名设置当前线程的Principal的类
/// </summary>
public static class SetThreadPrincipal
{
/// <summary>
/// 用于保存修改之前的Principal
/// </summary>
private static IPrincipal _oldPrincipal; /// <summary>
/// 设置当前线程的Principal,根据ApplicationContext里面的UserName是否存在来判断。
/// 通过Roles来获取对应UserName的角色。并将其设置到当前线程的CurrentPrincipal
/// </summary>
public static void SetThreadPrincipalToCurrentUser()
{
string username = ApplicationContext.Current.UserName;
if (!string.IsNullOrEmpty(username))
{
// 如果用户名为空,则说明不可能有用户信息,那就不用去取角色并放到线程中。
IIdentity identity;
identity = new GenericIdentity(username, Membership.Provider.Name); // TODO: Get Roles
string[] roles = Roles.GetRolesForUser(identity.Name);
IPrincipal principal = new GenericPrincipal(identity, roles); // Place user's principal on the thread
_oldPrincipal = Thread.CurrentPrincipal;
Thread.CurrentPrincipal = principal;
}
else
{// 如果用户名为空,则说明不可能有用户信息,那就不用去取角色并放到线程中。
// 这时候就不用设置oldPrincipal的值了。
_oldPrincipal = null;
}
} /// <summary>
/// 恢复线程的Principal
/// </summary>
public static void ResumeThreadPrincipal()
{
if (_oldPrincipal != null)
Thread.CurrentPrincipal = _oldPrincipal;
}
}
}
从代码中可以看到,如果取到上下文中的用户名之后就可以用户名对应的角色(关于Roles类的使用请微软搜索“角色提供”)。
然后我在ContextReceivalCallContextInitializer类的BeforeInvoke方法和AfterInvoke方法里面增加了对上面类的使用,代码如下:
ContextReceivalCallContextInitializer类在Infrastructure项目的WCF Extensions文件夹下。
public class ContextReceivalCallContextInitializer : ICallContextInitializer
{
private IPrincipal _oldPrincipal;
#region ICallContextInitializer Members public void AfterInvoke(object correlationState)
{
// 恢复Principal
SetThreadPrincipal.ResumeThreadPrincipal();
} public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
ApplicationContext.Current = message.Headers.GetHeader<ApplicationContext>(
ApplicationContext.ContextHeaderLocalName,
ApplicationContext.ContextHeaderNamespace);
// 设置Principal
SetThreadPrincipal.SetThreadPrincipalToCurrentUser();
return null;
} #endregion
}
以上代码添加完成后,就可以见证奇迹了。点击启动调试(前提是已经配置成功可以运行)
登录、浏览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);
}
这样,再次进行订单结账操作就能成功了。
这个问题到底是为什么呢。我真的百思不得其解啊。