2012-09-26 16 views
8

Tôi đang cố triển khai xác thực cho một dịch vụ REST được triển khai trong WCF và được lưu trữ trên Azure. Tôi đang sử dụng HttpModule để xử lý các sự kiện AuthenticationRequest, PostAuthenticationRequest và EndRequest. Nếu tiêu đề Cấp quyền bị thiếu hoặc nếu mã thông báo chứa trong đó không hợp lệ, trong EndRequest tôi đang đặt Mã trạng thái trên Phản hồi thành 401. Tuy nhiên, tôi đã xác định rằng EndRequest được gọi hai lần và cuộc gọi thứ hai phản hồi đã có tiêu đề thiết lập, gây ra mã mà đặt StatusCode để ném một ngoại lệ.Trình xử lý HttpModule EndRequest được gọi hai lần

Tôi đã thêm khóa vào Init() để đảm bảo rằng trình xử lý không được đăng ký hai lần; vẫn chạy hai lần. Init() cũng chạy hai lần, chỉ ra rằng hai cá thể của HttpModule đã được tạo ra. Tuy nhiên, bằng cách sử dụng Set Object ID trong trình gỡ lỗi VS dường như chỉ ra rằng các yêu cầu thực sự là các yêu cầu khác nhau. Tôi đã xác minh trong Fiddler rằng chỉ có một yêu cầu được cấp cho dịch vụ của tôi từ trình duyệt.

Nếu tôi chuyển sang sử dụng định tuyến toàn cầu.asax thay vì tùy thuộc vào cấu hình máy chủ lưu trữ dịch vụ WCF, trình xử lý chỉ được gọi một lần và mọi thứ hoạt động tốt.

Nếu tôi thêm cấu hình vào phần cấu hình system.web cũng như phần cấu hình system.webServer trong Web.config, trình xử lý chỉ được gọi một lần và mọi thứ hoạt động tốt.

Vì vậy, tôi có giảm nhẹ, nhưng tôi thực sự không thích hành vi mà tôi không hiểu. Tại sao trình xử lý được gọi hai lần?

Đây là một repro tối thiểu của vấn đề:

Web.config:

<system.web> 
    <compilation debug="true" targetFramework="4.0" /> 
    <!--<httpModules> 
     <add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/> 
    </httpModules>--> 
    </system.web> 
    <system.serviceModel> 
    <behaviors> 
     <endpointBehaviors> 
     <behavior name="WebBehavior"> 
      <webHttp/> 
     </behavior> 
     </endpointBehaviors> 
     <serviceBehaviors> 
     <behavior> 
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> 
      <serviceMetadata httpGetEnabled="true" /> 
      <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> 
      <serviceDebug includeExceptionDetailInFaults="true"/> 
     </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" /> 
    <services> 
     <service name="TestWCFRole.Service1"> 
     <endpoint binding="webHttpBinding" name="RestEndpoint" contract="TestWCFRole.IService1" bindingConfiguration="HttpSecurityBinding" behaviorConfiguration="WebBehavior"/> 
     <host> 
      <baseAddresses> 
      <add baseAddress="http://localhost/" /> 
      </baseAddresses> 
     </host> 
     </service> 
    </services> 
    <standardEndpoints> 
     <webHttpEndpoint> 
     <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/> 
     </webHttpEndpoint> 
    </standardEndpoints> 
    <bindings> 
     <webHttpBinding> 
     <binding name="HttpSecurityBinding" > 
      <security mode="None" /> 
     </binding> 
     </webHttpBinding> 
    </bindings> 
    </system.serviceModel> 
    <system.webServer> 
    <modules runAllManagedModulesForAllRequests="true"> 
     <add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/> 
    </modules> 
    <directoryBrowse enabled="true"/> 
    </system.webServer> 

Http mô-đun:

using System; 
using System.Web; 

namespace TestWCFRole 
{ 
    public class AuthModule : IHttpModule 
    { 
     /// <summary> 
     /// You will need to configure this module in the web.config file of your 
     /// web and register it with IIS before being able to use it. For more information 
     /// see the following link: http://go.microsoft.com/?linkid=8101007 
     /// </summary> 
     #region IHttpModule Members 

     public void Dispose() 
     { 
      //clean-up code here. 
     } 

     public void Init(HttpApplication context) 
     { 
      // Below is an example of how you can handle LogRequest event and provide 
      // custom logging implementation for it 
      context.EndRequest += new EventHandler(OnEndRequest); 
     } 

     #endregion 

     public void OnEndRequest(Object source, EventArgs e) 
     { 
      HttpContext.Current.Response.StatusCode = 401; 
     } 
    } 
} 
+0

Bạn có sử dụng UrlRewrite trong ứng dụng của bạn? Dường như, nó khiến EndRequest bắn hai lần. –

+0

Có thể bật trong nền không? Tôi không có UrlRewriteModule trong web.config của tôi. –

+0

Tôi không nghĩ vậy. Điều này thực sự kỳ lạ, bởi vì tôi không thể nói rằng tất cả các vấn đề của tôi gây ra bởi UrlRewriteModule. Nhưng một số trong số đó là. –

Trả lời

2

Xin lỗi không biết tại sao nó có thể được gọi hai lần, tuy nhiên EndRequest có thể sẽ bị gọi vì nhiều lý do. yêu cầu đã hoàn thành, yêu cầu đã bị hủy bỏ, một số lỗi đã xảy ra. Vì vậy, tôi sẽ không đặt niềm tin của tôi trong giả định rằng nếu bạn nhận được ở đó, bạn thực sự có một 401, nó có thể là vì lý do khác.

tôi chỉ muốn giữ logic của tôi trong các đường ống AuthenticateRequest:

public class AuthenticationModule : IHttpModule 
    { 
     public void Dispose() { } 

     public void Init(HttpApplication context) 
     { 
      context.AuthenticateRequest += Authenticate; 
     } 

     public static void Authenticate(object sender, EventArgs e) 
     { 
      // authentication logic here    
      //............. 

      if (authenticated) { 
       HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(myUser, myRoles); 
      } 

      // failure logic here   
      //.............   
     } 
    } 
+0

Vâng, tôi không thể hiểu tại sao tôi không nhận ra tôi nên tiếp cận nó như thế nào trước đây (tôi cho rằng tôi phải nghĩ rằng mẫu tôi đang sử dụng đã làm nó "đúng"), nhưng cuối cùng tôi đã chuyển logic vào AuthenticateRequest , PostAuthenticateRequest và AuthorizeRequest phần. Thật không may, điều này không trả lời câu hỏi là tại sao trình xử lý hoạt động theo cách này. –

7

Khi một ứng dụng ASP.net khởi động, để tối đa hóa hiệu suất quá trình ASP.NET Worker sẽ khởi tạo nhiều đối tượng HttpApplication khi cần. Mỗi đối tượng HttpApplication, cũng sẽ khởi tạo một bản sao của mỗi IHttpModule được đăng ký và gọi phương thức Init! Đó thực sự là một thiết kế nội bộ của quá trình ASP.NET chạy dưới IIS (hoặc cassini được xây dựng trong máy chủ web). Có thể là vì trang ASPX của bạn có liên kết đến các tài nguyên khác mà trình duyệt của bạn sẽ cố tải xuống, tài nguyên bên ngoài và iframe, tệp css hoặc có thể là hành vi của Quy trình làm việc ASP.NET.

May mắn nó không phải là trường hợp cho Global.asax:

Here's from MSDN:

Các phương pháp Application_Start và Application_End là phương pháp đặc biệt mà không đại diện cho HttpApplication sự kiện. ASP.NET gọi chúng một lần cho toàn bộ thời gian tồn tại của miền ứng dụng, không phải cho từng trường hợp HttpApplication.

Tuy nhiên phương pháp HTTPModule's init được gọi một lần cho tất cả các thể hiện của lớp HttpApplication sau khi tất cả các module đã được tạo ra

Lần đầu tiên một trang ASP.NET hoặc quá trình được yêu cầu trong một ứng dụng , một mới Ví dụ của HttpApplication được tạo ra.Tuy nhiên, để tối đa hóa hiệu suất, các phiên bản HttpApplication có thể được sử dụng lại cho nhiều yêu cầu.

Và minh họa bằng sơ đồ sau: enter image description here

Nếu bạn muốn mã đó là bảo đảm để chạy chỉ một lần, bạn có thể sử dụng Application_Start của Global.asax hoặc thiết lập một lá cờ và khóa nó trong module cơ bản mà không nghĩ là một thực hành tốt vì mục đích Xác thực!

+0

Tôi chỉ muốn thiết lập 'runAllManagedModulesForAllRequests =" true "' sẽ gây ra việc thực hiện mô-đun tùy chỉnh cho mỗi và mọi yêu cầu, bao gồm các tệp tĩnh (JS/CSS/Images). Vì vậy, chỉ hai lần cho một lần tải trang dường như quá ít đối với tôi với thiết lập đó. – astaykov

+0

Tôi không cần mã để chạy một lần; vấn đề là tôi thấy hai EndRequests cho một yêu cầu duy nhất cho dịch vụ. Nó không nên là JS/CSS/Images, bởi vì đây là một dịch vụ, không phải là một trang web. Tôi có thể thấy trong Fiddler rằng chỉ có một yêu cầu duy nhất đang được thực hiện. Câu hỏi đặt ra là tại sao tôi thấy một yêu cầu duy nhất thông qua Fiddler nhưng hai yêu cầu trong mô-đun và tại sao vấn đề này biến mất nếu tôi sao chép cấu hình trong web.config. –

+0

Thông thường bạn không cần phải sao chép cấu hình, nếu bạn đang sử dụng IIS7 + cấu hình phải được đặt trong phần 'system.webServer'! Phương thức 'Init' của Module được gọi một lần trong khi' EndRequest' được gọi hai lần cho một yêu cầu duy nhất? bởi vì 'EndRequest' không thể được gọi hai lần cho mỗi yêu cầu! Tuy nhiên tôi nhận thấy rằng bạn đang kích hoạt 'aspNetCompatibilityEnabled' trong cấu hình máy chủ lưu trữ WCF của bạn, bạn đang thử nghiệm máy chủ web nào? IIS? IIS Express? VS Web Server? Làm thế nào để bạn lưu trữ WCF của bạn trong IIS? Một 'WebServiceHost'? Tệp '.svc'? Bạn có thể đăng 'HttpContext.Current.Request.Path' trong EndRequest không? –

Các vấn đề liên quan