代码改变世界

WCF REST & ASP.NET MVC authorization

  AnyKoro  阅读(1257)  评论(0编辑  收藏  举报

Last week I needed to implement an authorization scheme in our MVC and WCF apps. I found a bunch of resources on how to implement Role or Claims-based authorization in both frameworks, but they all required adding CLR attributes on controller actions and service operations - a bit of messy for my taste, and required hard-coding your authorization rules, which didn’t fit my requirements. We developed a system that allow us to define authorization rules at run-time, and I was looking for a way to write a single authorization front controller for all service / MVC calls. I could have gone the HTTP module way, but that would have mean writing code to parse the request & figure out what controller, contract, operation are being called with what parameters. Since all of this is already done for in the MVC and WCF frameworks, I wanted to have this authorization controller called after the request is parsed and before the operation is executed.

01.public class MyAuthorizedController {
02.   protected override void OnActionExecuting(ActionExecutingContext filterContext)
03. {
04.           string targetAction = filterContext.ActionDescriptor.ActionName;
05.            string idParameter = filterContext.ActionParameters["id"];
06.              string username = filterContext.HttpContext.User.Identity.Name;
07.             bool isAllowed = IAuthorizationManager.Validate(username, idParameter , targetAction);
08.              if(isAllowed)
09.               base.OnActionExecuting(filterContext);
10.              throw new UnauthorizedAccessException();
11.    }
12.}

 

 

Now just make sure all your secured controllers extend this class, and you’re done.

 

 

WCF looked just as easy at first. In the ServiceBehavior configuration, you can define a custom authorization class in the serviceAuthorization’s ServiceAuthorizationManagerType property. The custom class needs to extend ServiceAuthorizationManager, which exposes a protected virtual method called “CheckAccessCore”. Just override that method.

 

 

01.public class MyAuthorizationManager : System.ServiceModel.ServiceAuthorizationManager
02.{
03.  protected override bool CheckAccessCore(OperationContext operationContext)
04.        {
05.     // If using ASP.NET authentication, we can retrieve the user name from HttpContext
06.      // Of course this won't work for anonymous requests, you;d need to handle those as well
07.     var userName = HttpContext.Current.User.Identity;
08. 
09.       // the target service is the actual service class being called.
10.        var targetService = operationContext.Host.Description.ServiceType;
11.       
12.        // not used in the specfic authorization manager, but could be useful depending on your scenario
13.        var contractDescription = getOperationContractType(operationContext);
14. 
15.       // The following key will help check whether or not this was a valid REST request
16.       // If the key is found, we'll retrieve the UriTemplateMatch results which will give us info about operation name and
17.               string key = WebHttpDispatchOperationSelector.HttpOperationSelectorUriMatchedPropertyName;
18.      var props = operationContext.IncomingMessageProperties;
19.             UriTemplateMatch match = null;
20.              if (props.ContainsKey(key) && (bool)props[key])
21.                 match = operationContext.IncomingMessageProperties["UriTemplateMatchResults"] as UriTemplateMatch;
22.      else
23.            // in this case, we'll ignore all non-REST requests
24.         return true;
25. 
26.        // now that we've found the UriTemplateMatch instance, we can find information about our method parameters
27.      // we'll use 'id' as an example
28.     if(match.BoundVariables.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
29.                idValue = match.BoundVariables["id"];
30. }
31. 
32. 
33.   // For information about the data contract (the interface mapped to the endpoint being used),
34.   // you'll need to loop through all EndPoints in operationContext.Host.Description.Endpoints and look for
35.   // one that has the same namespace and contract name as your context's EndpointDispatcher
36.   private static Dictionary<string ,="" contractdescription=""> contractMap = new Dictionary<string ,="" contractdescription="">();
37.        private static ContractDescription getOperationContractType(OperationContext operationContext)
38.        {
39.            if(contractMap.Count == 0){
40.                lock(contractMap){
41.                    ServiceEndpointCollection endpoints = operationContext.Host.Description.Endpoints;
42.                   foreach (ServiceEndpoint endpoint in endpoints) {
43.                       string contractKey = endpoint.Contract.Namespace + endpoint.Contract.Name;
44.                       contractMap[contractKey] = endpoint.Contract;
45.                   }
46.                }
47.            }
48.            EndpointDispatcher dispatcher = operationContext.EndpointDispatcher;
49.            string key = dispatcher.ContractNamespace +  dispatcher.ContractName;
50.            return contractMap[key];
51.        }
52.}</string></string>

 

 

So now we’ve found out the current user's username, the service’s type, the value of our id parameter, and the datacontract’s description. We’re getting close but – where’s the actual operation name (the name of the method being called)??

 

 

When using SOAP, you can easily find the action being called by using operationContext.IncomingMessageHeaders.Action. The action looks like this: http://MyService/IMyContract/MyAction1. This would be easy to parse.

 

 

Unfortunately, when using REST EndPoints, the Action message header is blank. So where is it the information stored? In an obscure untyped “data” property found in the UriTemplateMatch. When debugging, you’ll see that this property is actually an instance of type WebHttpDispatchOperationSelector+WCFLookupResult, which is a private class. It exposes 2 properties – Method (HTTP method) and OperationName (the method called on the service, which is what we’re looking for).

 

 

Why the WCF team would decide to hide the OperationName is beyond me. I kept thinking I was missing something, I went through every single property of the OperationContext class (a never-ending project), and I just couldn’t find it anywhere. How are you supposed to authorize a service call if you don’t know what action is called on the service??

 

 

My current workaround is to use reflection. Here’s the method:

 

 

01.protected override bool CheckAccessCore(OperationContext operationContext)
02.     {
03.  [...]
04.    string operationName = getOperationName(match.Data);
05.     bool isAuthorized = IAuthorizationManager.Validate(username, id, operationName);
06. }
07. 
08.private static object operationNameLock = new Object();
09.     private static PropertyInfo operationNamePropertyInfo;
10.     private static string getOperationName(object data){
11.         if(operationNamePropertyInfo == null){
12.             lock(operationNameLock){
13.                 operationNamePropertyInfo = data.GetType().GetProperty("OperationName");
14.             }
15.         }
16.         try{
17.             return operationNamePropertyInfo == null ? null : operationNamePropertyInfo.GetValue(data, null) as string;
18.         }catch{
19.             return null;
20.         }
21.     }
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示