2008-10-26 30 views
234

Trong ASP.NET MVC, bạn có thể đánh dấu một phương pháp điều khiển với AuthorizeAttribute, như thế này:Tại sao AuthorizeAttribute chuyển hướng đến trang đăng nhập để xác thực và ủy quyền thất bại?

[Authorize(Roles = "CanDeleteTags")] 
public void Delete(string tagName) 
{ 
    // ... 
} 

Điều này có nghĩa rằng, nếu hiện đăng nhập người dùng không nằm trong "CanDeleteTags" vai trò, bộ điều khiển phương pháp sẽ không bao giờ được gọi.

Thật không may, đối với lỗi, AuthorizeAttribute trả về HttpUnauthorizedResult, mã này luôn trả về mã trạng thái HTTP 401. Điều này dẫn đến chuyển hướng đến trang đăng nhập.

Nếu người dùng không đăng nhập, điều này có ý nghĩa hoàn hảo. Tuy nhiên, nếu người dùng là đã được đăng nhập nhưng không nằm trong vai trò bắt buộc, thật khó hiểu khi gửi họ trở lại trang đăng nhập.

Dường như AuthorizeAttribute conflates xác thực và ủy quyền.

Điều này có vẻ giống như một chút giám sát trong ASP.NET MVC, hoặc tôi thiếu cái gì?

Tôi đã phải nấu một số DemandRoleAttribute để tách hai. Khi người dùng không được xác thực, nó trả về HTTP 401, gửi chúng đến trang đăng nhập. Khi người dùng đăng nhập, nhưng không phải là vai trò được yêu cầu, nó sẽ tạo ra một NotAuthorizedResult thay thế. Hiện tại, chuyển hướng này đến trang lỗi.

Chắc chắn tôi không phải làm điều này?

+7

câu hỏi tuyệt vời và tôi đồng ý, nó nên ném trạng thái HTTP Not Authorized. –

+2

Tôi thích giải pháp của bạn, Roger. Ngay cả khi bạn không. –

+0

Trang Đăng nhập của tôi có séc chỉ chuyển hướng người dùng đến ReturnUrl, nếu anh/chị ấy đã được xác thực. Vì vậy, tôi quản lý để tạo ra một vòng lặp vô hạn của 302 chuyển hướng: D woot. –

Trả lời

282

Khi phát triển lần đầu tiên, System.Web.Mvc.AuthorizeAttribute đã làm điều đúng - các phiên bản cũ hơn của đặc tả HTTP đã sử dụng mã trạng thái 401 cho cả "trái phép" và "chưa được xác thực".

Từ đặc điểm kỹ thuật ban đầu:

If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials.

Trong thực tế, bạn có thể thấy sự nhầm lẫn ngay tại đó - nó sử dụng từ "uỷ quyền" khi nó có nghĩa là "xác thực". Trong thực tế hàng ngày, tuy nhiên, nó có ý nghĩa hơn để trả lại một 403 Cấm khi người dùng được xác thực nhưng không được ủy quyền. Không chắc người dùng sẽ có tập thông tin đăng nhập thứ hai sẽ cung cấp cho họ quyền truy cập - trải nghiệm người dùng tồi khắp nơi.

Hãy xem xét hầu hết các hệ điều hành - khi bạn cố đọc một tệp mà bạn không có quyền truy cập, bạn sẽ không hiển thị màn hình đăng nhập!

Rất may, thông số kỹ thuật HTTP đã được cập nhật (tháng 6 năm 2014) để xóa sự mơ hồ.

Từ "Hyper Text Transport Protocol (HTTP/1.1): Xác thực" (RFC 7235):

The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.

Từ "Hypertext Transfer Protocol (HTTP/1.1): Semantics và Nội dung" (RFC 7231):

The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it.

Điều thú vị đủ, tại thời điểm ASP.NET MVC 1 được phát hành, hành vi của AuthorizeAttribute là chính xác. Bây giờ, hành vi không chính xác - đặc tả HTTP/1.1 đã được sửa.

Thay vì cố gắng thay đổi chuyển hướng trang đăng nhập của ASP.NET, sẽ dễ dàng hơn để khắc phục sự cố tại nguồn. Bạn có thể tạo thuộc tính mới có cùng tên (AuthorizeAttribute) trong không gian tên mặc định của trang web (điều này rất quan trọng), trình biên dịch sẽ tự động chọn nó thay vì tiêu chuẩn của MVC. Tất nhiên, bạn luôn có thể đặt tên cho thuộc tính nếu bạn muốn sử dụng phương pháp đó.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] 
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute 
{ 
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext) 
    { 
     if (filterContext.HttpContext.Request.IsAuthenticated) 
     { 
      filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden); 
     } 
     else 
     { 
      base.HandleUnauthorizedRequest(filterContext); 
     } 
    } 
} 
+48

+1 Cách tiếp cận rất tốt. Một gợi ý nhỏ: thay vì kiểm tra 'filterContext.HttpContext.User.Identity.IsAuthenticated', bạn chỉ có thể kiểm tra' filterContext.HttpContext.Request.IsAuthenticated', đi kèm với kiểm tra null được tích hợp. Xem http://stackoverflow.com/ câu hỏi/1379566/what-is-the-sự khác biệt-giữa-httpcontext-current-request-isauthenticated-and-ht/1379601 # 1379601 –

+5

Ý tưởng tuyệt vời - Tôi đã cập nhật câu trả lời. – ShadowChaser

+0

> Bạn có thể tạo thuộc tính mới có cùng tên (AuthorizeAttribute) trong không gian tên mặc định của trang web của bạn sau đó trình biên dịch sẽ tự động chọn nó thay vì tiêu chuẩn của MVC. Điều này dẫn đến lỗi: Không thể tìm thấy loại hoặc không gian tên 'Ủy quyền' (bạn thiếu một chỉ thị hoặc tham chiếu assembly?) Cả hai đều sử dụng System.Web.Mvc; và không gian tên cho lớp AuthorizeAttribute tùy chỉnh của tôi được tham chiếu trong bộ điều khiển. Để giải quyết vấn đề này, tôi phải sử dụng [MyNamepace.Authorize] – stormwild

4

Thật không may, bạn đang xử lý hành vi mặc định của xác thực biểu mẫu ASP.NET. Có một cách giải quyết (Tôi đã không thử nó) thảo luận ở đây:

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(Nó không cụ thể cho MVC)

Tôi nghĩ rằng trong hầu hết các trường hợp, các giải pháp tốt nhất là để hạn chế quyền truy cập vào các nguồn tài nguyên trái phép trước khi người dùng cố gắng đến đó. Bằng cách xóa/bôi xám liên kết hoặc nút có thể đưa họ đến trang trái phép này.

Có thể sẽ tốt hơn khi có thông số bổ sung trên thuộc tính để chỉ định nơi chuyển hướng người dùng trái phép. Nhưng trong khi chờ đợi, tôi nhìn vào AuthorizeAttribute như một mạng lưới an toàn.

+0

Tôi cũng có kế hoạch loại bỏ liên kết dựa trên ủy quyền (tôi thấy một câu hỏi ở đây về điều đó ở đâu đó), vì vậy tôi sẽ viết một phương thức mở rộng HtmlHelper sau. –

+1

Tôi vẫn phải ngăn người dùng truy cập trực tiếp vào URL, thuộc tính này là gì. Tôi không hài lòng với giải pháp Custom 401 (có vẻ hơi toàn cầu), vì vậy tôi sẽ thử mô hình hóa NotAuthorizedResult của mình trên RedirectToRouteResult ... –

4

Tôi luôn nghĩ rằng điều này có ý nghĩa. Nếu bạn đã đăng nhập và bạn cố gắng nhấn một trang yêu cầu vai trò mà bạn không có, bạn sẽ được chuyển tiếp đến màn hình đăng nhập yêu cầu bạn đăng nhập bằng người dùng có vai trò.

Bạn có thể thêm logic vào trang đăng nhập để kiểm tra xem liệu người dùng đã được xác thực hay chưa. Bạn có thể thêm một thông điệp thân thiện giải thích tại sao họ lại bị bẻ cong ở đó một lần nữa.

+4

Đó là cảm giác của tôi rằng hầu hết mọi người không có xu hướng có nhiều hơn một danh tính cho một cho ứng dụng web. Nếu họ làm vậy, họ đủ thông minh để nghĩ "ID hiện tại của tôi không có mojo, tôi sẽ đăng nhập lại với tư cách là người khác". –

+0

Mặc dù điểm khác của bạn về việc hiển thị nội dung nào đó trên trang đăng nhập là một điều tốt. Cảm ơn. –

23

Thêm phần này vào chức năng Page_Load nhập của bạn:

// User was redirected here because of authorization section 
if (User.Identity != null && User.Identity.IsAuthenticated) 
    Response.Redirect("Unauthorized.aspx"); 

Khi người dùng được chuyển hướng có nhưng đã đăng nhập, nó cho thấy các trang trái phép. Nếu họ không đăng nhập, nó rơi qua và hiển thị trang đăng nhập.

+17

Page_Load là một biểu mẫu web mojo – Chance

+2

@Chance - sau đó thực hiện điều đó trong ActionMethod mặc định cho bộ điều khiển được gọi là nơi FormsAuthencation đã được thiết lập để gọi. –

+0

Điều này thực sự làm việc thực sự tốt mặc dù cho MVC nó phải là một cái gì đó như 'if (User.Identity! = Null && User.Identity.IsAuthenticated) trả về RedirectToRoute (" trái phép ");' nơi * trái phép * là một tên tuyến đường được xác định. –

-1

Hãy thử này trong thư mục trong xử lý Application_EndRequest của Global.ascx bạn nộp

if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/")) 
{ 
    HttpContext.Current.Response.ClearContent(); 
    Response.Redirect("~/AccessDenied.aspx"); 
} 
0

Nếu bạn sử dụng aspnetcore 2.0, sử dụng này:

using System; 
using Microsoft.AspNetCore.Mvc; 
using Microsoft.AspNetCore.Mvc.Filters; 

namespace Core 
{ 
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] 
    public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter 
    { 
     public void OnAuthorization(AuthorizationFilterContext context) 
     { 
      var user = context.HttpContext.User; 

      if (!user.Identity.IsAuthenticated) 
      { 
       context.Result = new UnauthorizedResult(); 
       return; 
      } 
     } 
    } 
} 
Các vấn đề liên quan