65.9K
CodeProject 正在变化。 阅读更多。
Home

提高 WCF 服务质量(第三部分 - 身份验证和授权)

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2022 年 2 月 28 日

CPOL

4分钟阅读

viewsIcon

4014

如何向 WCF 服务添加身份验证和授权

引言

在上一个部分这里,我们讨论了如何添加验证。本部分将解释我如何为WCF服务添加身份验证和授权。

Using the Code

为了将身份验证和授权过程引入我们的服务,我们将使用面向切面编程(AOP)的概念,并且如第一章所述,我们将为此目的使用PostSharp

第一步,让我们创建一个新的类库项目,并命名为WCFService.Extensions。此项目将引用WCFLibrary.Shared,并将被所有服务库引用。接下来,我们需要将ServiceMethodAspect类添加到新的类库项目中。

添加ServiceMethodAspect类后,我们将让我们的类继承自OnMethodBoundaryAspect类,并用三个重写的方法来装饰我们的类:OnEntryOnExitOnException

public class ServiceMethodAspect : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
    }
    public override void OnExit(MethodExecutionArgs args)
    {
    }
    public override void OnException(MethodExecutionArgs args)
    {
    }
}

完成此操作后,您会发现OnMethodBoundaryAspect类无法识别。因此,请前往NuGet程序包管理器控制台,安装PostSharpPostSharp.Redist库,并添加对PostSharp.Aspects命名空间的引用。

PostSharp libraries : NuGet

此时,您的项目将无法成功生成,因为PostSharp在项目中使用需要有效的许可证,而我们还没有许可证。如果您符合要求,可以从此页面获取免费许可证。此外,许可证的部署描述在此页面。但最简单的方法(如果您不介意在Visual Studio中安装工具集)是访问此页面,安装扩展程序,仅此而已。许可证将自动部署,您将能够再次生成应用程序。

那么这两个方法将做什么呢?顾名思义,OnEntry方法将处理在方法执行之前执行的操作。一旦调用您的服务方法,在执行与您的服务相关的任何内容之前,都会触发此方法。因此,这是检查身份验证和授权的好地方。但是您会发现,我们目前还没有关于请求对象的信息。这很有道理,因为我们的切面不知道哪个方法将使用哪些参数被调用。因此,我们应该找到一种方法来访问我们的输入参数。首先,让我们在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 AuthenticationObject).Username;
        password = (arg as AuthenticationObject).Password;
    }
}

这段代码的作用是获取调用方法时传入的参数args.Arguments,并在类型为BaseRequest的对象中查找UsernamePassword属性以获取其值。

执行这段代码后,我们现在就拥有了输入参数中UsernamePassword属性的值。我认为这与我们在第一章所做的相符(为每个方法创建请求类作为输入参数,并让所有请求类继承自一个基类,该基类确保每个方法的请求类作为输入都具有UsernamePassword属性。在获得必要的值后,我们就可以先检查身份验证,然后检查授权,如果身份验证成功,则返回结果。

现在我们发现需要返回一个正确的响应,我们就需要弄清楚如何实现。再次参考第一章,请记住,我们将所有方法的返回类型都更改为继承BaseResponse类的响应类,该类具有类型分别为boolboolstring[]IsExceptionIsSuccessMessages属性。要访问我们的响应对象,我们需要编写以下代码:

Type returnType = ((System.Reflection.MethodInfo)args.Method).ReturnType;
object returnObj = Activator.CreateInstance(returnType);
MethodBase mb = args.Method;

这段代码做什么?首先,它通过args.Methods获取正在执行的方法,并将其转换为System.Reflection.MethodInfo类型,以发现方法的属性并提供对方法元数据的访问,最后通过访问ReturnType属性获取方法的返回类型。现在我们准备好检查身份验证和授权了。因此,让我们切换回我们的WCFService.Utils项目,添加一个名为Auth的文件夹,并分别添加两个新类,名为AuthenticationManagerAuthorizationManager,并按如下方式装饰它们:

public class AuthenticationManager
{
    public int? Authenticate(string username, string password, out string message)
    {
        message = null;
        // Do your authentication work here and return UserId if authenticated, null otherwise.
    }
}
public class AuthorizationManager
{
    public bool IsAuthorized(int userId, string FullyQualifiedServiceName, string MethodName)
    {
        // Do your authorization work here according to the UserId, ServiceName and 
        // ServiceMethod name and return TRUE if authenticated, FALSE otherwise
    }
}

AuthenticateIsAuthorized方法中的代码有意留空,以便用户实现自己的身份验证和授权过程。这可能使用本地数据库、云数据库、JSON文件、文本文件、注册表记录,任何您喜欢的方式。以防万一,请记住声明了一个integer类型的标识符来保存用户数据。根据您的实现,此类型也可能根据您的选择而更改。

现在我们已经描述了所有必要的部分,让我们将所有部分组合在一起,使身份验证和授权过程能够正常工作。

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)
    {   // User is authenticated, check authorization
        AuthorizationManager authorize = new AuthorizationManager();
        bool isAuthorized = authorize.IsAuthorized
                            (userId.Value, mb.DeclaringType.FullName, mb.Name);
        if (isAuthorized)
        {   // User is also authorized on using this services particular method
            // Set base response as OK for this authentication and authorization.
            // Note that this value will possibly be overwritten 
            // by the service method itself, so may be skipped
            BaseResponse response = new BaseResponse
            {
                IsException = false,
                IsSuccess = true,
                Messages = null
            };
            // Set execution flow to continue to method execution
            args.FlowBehavior = FlowBehavior.Continue;
            // Set the return value as BaseResponse object
            args.ReturnValue = response;
        }
        else
        {   // Authorization failed, return proper response
            // Get IsSuccess, IsException and Messages properties of the BaseRespone object
            PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
            PropertyInfo piIsException = returnType.GetProperty("IsException");
            PropertyInfo piMessages = returnType.GetProperty("Messages");
            // Set proper values on BaseReponse object
            piIsSuccess.SetValue(returnObj, false);,
            piIsException.SetValue(returnObj, false);
            piMessages.SetValue(returnObj, new string[] { $"You are not authorized to call 
                       this service method ({mb.DeclaringType.FullName} - {mb.Name})" });
            // Set execution flow to return
            args.FlowBehavior = FlowBehavior.Return;
            // Set the return value as BaseResponse object
            args.ReturnValue = returnObj;
        }
    }
    else
    {   // Authentication failed, return proper response
        // Get IsSuccess, IsException and Messages properties of the BaseRespone object
        PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
        PropertyInfo piIsException = returnType.GetProperty("IsException");
        PropertyInfo piMessages = returnType.GetProperty("Messages");
        // Set proper values on BaseReponse object
        piIsSuccess.SetValue(returnObj, false);
        piIsException.SetValue(returnObj, false);
        piMessages.SetValue(returnObj, new string[] { authenticationMessage });
        // Set execution flow to return
        args.FlowBehavior = FlowBehavior.Return;
        // Set the return value as BaseResponse object
        args.ReturnValue = returnObj;
    }
}

这是我们系列文章的第三部分,介绍了如何为我们的服务请求添加身份验证和授权过程。

您可以在这里阅读下一部分(日志记录)。

历史

  • 2022 年 2 月 28 日:初始版本
© . All rights reserved.