在现代的软件开发中,安全性是一个不可忽视的重要方面。对于基于Windows Communication Foundation (WCF) 的服务来说,实现认证和授权机制是保护服务不被未授权访问的关键步骤。本文将介绍如何使用面向切面编程(Aspect Oriented Programming, AOP)的概念和PostSharp工具来为WCF服务添加认证和授权。
首先,需要创建一个新的类库项目,命名为WCFService.Extensions。这个项目将引用WCFLibrary.Shared,并被所有的服务库引用。接下来,将在这个新项目中添加一个名为ServiceMethodAspect的类。
在ServiceMethodAspect类中,将继承OnMethodBoundaryAspect类,并重写OnEntry、OnExit和OnException方法。这三个方法分别对应方法执行前的入口点、执行后的出口点和异常处理点。
public class ServiceMethodAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
}
public override void OnExit(MethodExecutionArgs args)
{
}
public override void OnException(MethodExecutionArgs args)
{
}
}
在这一步,可能会发现OnMethodBoundaryAspect类未被识别。为了解决这个问题,需要通过NuGet包管理器安装PostSharp和PostSharp.Redist库,并添加对PostSharp.Aspects命名空间的引用。
PostSharp需要一个有效的许可证才能在项目中使用。如果满足条件,可以从获取免费许可证。许可证的部署说明可以在找到。如果不介意在Visual Studio中安装一个工具集,可以访问,安装扩展,许可证将自动部署,就可以再次构建应用程序了。
OnEntry方法将在服务方法被调用之前执行,这是检查认证和授权的好时机。但是,还没有关于请求对象的信息。因此,需要找到一种方法来访问输入参数。
base.OnEntry(args);
string username = "";
string password = "";
Arguments methodArgs = args.Arguments;
foreach (var arg in methodArgs)
{
if (arg as BaseRequest != null)
{
username = (arg as BaseRequest).Username;
password = (arg as BaseRequest).Password;
}
}
这段代码通过调用args.Arguments获取方法被调用时的参数,并在BaseRequest类型的对象中查找Username和Password属性的值。
在获取了Username和Password的值之后,现在可以进行认证检查,然后是授权检查。为了访问响应对象,需要编写以下代码:
Type returnType = ((System.Reflection.MethodInfo)args.Method).ReturnType;
object returnObj = Activator.CreateInstance(returnType);
MethodBase mb = args.Method;
这段代码首先获取正在执行的方法,将其转换为System.Reflection.MethodInfo类型,以便访问方法的元数据,并最终通过ReturnType属性获取方法的返回类型。
接下来,需要在WCFService.Utils项目中添加一个名为Auth的文件夹,并添加两个名为AuthenticationManager和AuthorizationManager的新类。
public class AuthenticationManager
{
public int? Authenticate(string username, string password, out string message)
{
message = null;
// 在这里实现认证工作,并返回用户ID,如果认证失败则返回null。
}
}
public class AuthorizationManager
{
public bool IsAuthorized(int userId, string FullyQualifiedServiceName, string MethodName)
{
// 根据用户ID、服务名称和服务方法名称实现授权工作,并返回TRUE如果认证通过,否则返回FALSE。
}
}
这两个类中的代码是故意留空的,以便用户可以根据自己的需要实现自己的认证和授权过程。这可能涉及到本地数据库、云数据库、JSON文件、文本文件、注册表记录等。
现在已经描述了所有必要的部分,让把所有的部分整合在一起,使认证和授权过程工作起来。
public override void OnEntry(MethodExecutionArgs args)
{
base.OnEntry(args);
string username = "";
string password = "";
Arguments methodArgs = args.Arguments;
foreach (var arg in methodArgs)
{
if (arg as BaseRequest != null)
{
username = (arg as BaseRequest).Username;
password = (arg as BaseRequest).Password;
}
}
Type returnType = ((System.Reflection.MethodInfo)args.Method).ReturnType;
object returnObj = Activator.CreateInstance(returnType);
MethodBase mb = args.Method;
AuthenticationManager authenticate = new AuthenticationManager();
string authenticationMessage = null;
int? userId = authenticate.Authenticate("", "", out authenticationMessage);
if (userId.HasValue)
{
// 用户已认证,检查授权
AuthorizationManager authorize = new AuthorizationManager();
bool isAuthorized = authorize.IsAuthorized(userId.Value, mb.DeclaringType.FullName, mb.Name);
if (isAuthorized)
{
// 用户也已授权使用此服务的特定方法
// 设置基础响应为OK
BaseResponse response = new BaseResponse
{
IsException = false,
IsSuccess = true,
Messages = null
};
// 设置执行流继续执行方法
args.FlowBehavior = FlowBehavior.Continue;
// 设置返回值为BaseResponse对象
args.ReturnValue = response;
}
else
{
// 授权失败,返回适当的响应
PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
PropertyInfo piIsException = returnType.GetProperty("IsException");
PropertyInfo piMessages = returnType.GetProperty("Messages");
piIsSuccess.SetValue(returnObj, false);
piIsException.SetValue(returnObj, false);
piMessages.SetValue(returnObj, new string[] { "没有权限调用此服务方法" });
args.FlowBehavior = FlowBehavior.Return;
args.ReturnValue = returnObj;
}
}
else
{
// 认证失败,返回适当的响应
PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
PropertyInfo piIsException = returnType.GetProperty("IsException");
PropertyInfo piMessages = returnType.GetProperty("Messages");
piIsSuccess.SetValue(returnObj, false);
piIsException.SetValue(returnObj, false);
piMessages.SetValue(returnObj, new string[] { authenticationMessage });
args.FlowBehavior = FlowBehavior.Return;
args.ReturnValue = returnObj;
}
}