WCF REST & ASP.NET MVC authorization

本文介绍了一种在MVC和WCF应用中实现统一运行时定义授权规则的方法,避免了硬编码授权规则的问题,并提供了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.      }

转载于:https://www.cnblogs.com/JosephLiu/archive/2010/02/19/1669283.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值