เหตุใด AuthorizeAttribute จึงเปลี่ยนเส้นทางไปที่หน้าเข้าสู่ระบบเพื่อตรวจสอบสิทธิ์และล้มเหลว


265

ใน ASP.NET MVC คุณสามารถทำเครื่องหมายวิธีควบคุมด้วยAuthorizeAttributeเช่น:

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

ซึ่งหมายความว่าหากผู้ใช้ที่เข้าสู่ระบบในปัจจุบันไม่ได้อยู่ในบทบาท "CanDeleteTags" วิธีการควบคุมจะไม่ถูกเรียก

น่าเสียดายสำหรับความล้มเหลวAuthorizeAttributeจะส่งคืนHttpUnauthorizedResultซึ่งจะส่งคืนรหัสสถานะ HTTP 401 เสมอซึ่งจะทำให้การเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ

หากผู้ใช้ไม่ได้เข้าสู่ระบบนี่เหมาะสมอย่างยิ่ง อย่างไรก็ตามหากผู้ใช้เข้าสู่ระบบแล้วแต่ไม่ได้อยู่ในบทบาทที่ต้องการมันก็สับสนที่จะส่งพวกเขากลับไปที่หน้าเข้าสู่ระบบ

ดูเหมือนว่าการAuthorizeAttributeพิสูจน์ตัวตนและการอนุญาต conflates

ดูเหมือนว่าการกำกับดูแลใน ASP.NET MVC หรือฉันขาดอะไรไป?

ฉันต้องทำอาหารDemandRoleAttributeที่แยกทั้งสอง เมื่อผู้ใช้ไม่ได้รับการรับรองความถูกต้องก็จะส่งคืน HTTP 401 ส่งไปยังหน้าเข้าสู่ระบบ เมื่อผู้ใช้เข้าสู่ระบบ แต่ไม่ได้อยู่ในบทบาทที่ต้องการก็จะสร้างขึ้นNotAuthorizedResultแทน ปัจจุบันนี้เปลี่ยนเส้นทางไปยังหน้าข้อผิดพลาด

แน่นอนฉันไม่ต้องทำเช่นนี้?


10
คำถามที่ยอดเยี่ยมและฉันเห็นด้วยก็ควรจะโยนสถานะ HTTP ไม่ได้รับอนุญาต
Pure.Krome

3
ฉันชอบทางออกของคุณโรเจอร์ แม้ว่าคุณจะไม่
Jon Davis

หน้าเข้าสู่ระบบของฉันมีการตรวจสอบเพียงแค่เปลี่ยนเส้นทางผู้ใช้ไปที่ ReturnUrl หาก s / เขาได้รับการแจ้งเตือนอัตโนมัติแล้ว ดังนั้นฉันจัดการเพื่อสร้างวงวนไม่สิ้นสุดของการเปลี่ยนเส้นทาง 302: D woot
juhan_h

1
ตรวจสอบ นี้
Jogi

Roger บทความดีดีในการแก้ปัญหาของคุณ - red-gate.com/simple-talk/dotnet/asp-net/ …ดูเหมือนว่าโซลูชันของคุณเป็นวิธีเดียวที่จะทำสิ่งนี้อย่างหมดจด
Craig

คำตอบ:


305

เมื่อได้รับการพัฒนาขึ้นครั้งแรก System.Web.Mvc.AuthorizeAttribute กำลังทำสิ่งที่ถูกต้อง - การแก้ไขเก่าของข้อกำหนด HTTP ที่ใช้รหัสสถานะ 401 สำหรับทั้ง "ไม่ได้รับอนุญาต" และ "ไม่ได้รับการรับรองความถูกต้อง"

จากสเปคเดิม:

หากคำร้องขอนั้นมีข้อมูลประจำตัวการอนุญาตแล้วการตอบสนอง 401 บ่งชี้ว่าการอนุญาตนั้นถูกปฏิเสธสำหรับข้อมูลรับรองเหล่านั้น

ในความเป็นจริงคุณสามารถเห็นความสับสนที่นั่น - มันใช้คำว่า "การอนุญาต" เมื่อมันหมายถึง "การรับรองความถูกต้อง" อย่างไรก็ตามในทางปฏิบัติในชีวิตประจำวันมันเหมาะสมกว่าที่จะส่งคืน 403 สิ่งต้องห้ามเมื่อผู้ใช้รับรองความถูกต้อง แต่ไม่ได้รับอนุญาต ไม่น่าเป็นไปได้ที่ผู้ใช้จะมีหนังสือรับรองชุดที่สองที่จะให้พวกเขาเข้าถึง - ประสบการณ์ผู้ใช้ที่ไม่ดีรอบตัว

พิจารณาระบบปฏิบัติการส่วนใหญ่ - เมื่อคุณพยายามอ่านไฟล์ที่คุณไม่มีสิทธิ์เข้าถึงคุณจะไม่เห็นหน้าจอเข้าสู่ระบบ!

โชคดีที่ข้อมูลจำเพาะ HTTP ได้รับการอัปเดต (มิถุนายน 2014) เพื่อลบความคลุมเครือ

จาก "Hyper Text Transport Protocol (HTTP / 1.1): การตรวจสอบความถูกต้อง" (RFC 7235):

รหัสสถานะ 401 (ไม่ได้รับอนุญาต) บ่งชี้ว่าการร้องขอนั้นไม่ได้ถูกนำไปใช้เพราะมันไม่มีข้อมูลรับรองการตรวจสอบสิทธิ์ที่ถูกต้องสำหรับทรัพยากรเป้าหมาย

จาก "Hypertext Transfer Protocol (HTTP / 1.1): ความหมายและเนื้อหา" (RFC 7231):

รหัสสถานะ 403 (ต้องห้าม) ระบุว่าเซิร์ฟเวอร์เข้าใจคำขอ แต่ปฏิเสธที่จะอนุญาต

น่าสนใจพอในขณะนั้น ASP.NET MVC 1 ถูกเผยแพร่พฤติกรรมของ AuthorizeAttribute นั้นถูกต้อง ตอนนี้พฤติกรรมไม่ถูกต้อง - ข้อมูลจำเพาะ HTTP / 1.1 ได้รับการแก้ไขแล้ว

แทนที่จะพยายามเปลี่ยนหน้าการเข้าสู่ระบบของ ASP.NET จะเป็นการง่ายกว่าที่จะแก้ไขปัญหาที่แหล่งที่มา คุณสามารถสร้างแอททริบิวใหม่ด้วยชื่อเดียวกัน ( AuthorizeAttribute) ในเนมสเปซเริ่มต้นของเว็บไซต์ของคุณ (สำคัญมาก) จากนั้นคอมไพเลอร์จะเลือกโดยอัตโนมัติแทน MVC มาตรฐาน แน่นอนว่าคุณสามารถตั้งชื่อใหม่ให้แอตทริบิวต์ได้เสมอหากคุณต้องการใช้วิธีการนั้น

[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);
        }
    }
}

52
+1 วิธีการที่ดีมาก ข้อเสนอแนะเล็ก ๆ : แทนการตรวจสอบfilterContext.HttpContext.User.Identity.IsAuthenticatedคุณสามารถตรวจสอบfilterContext.HttpContext.Request.IsAuthenticatedซึ่งมาพร้อมกับการตรวจสอบโมฆะในตัวดูstackoverflow.com/questions/1379566//
แดเนียล Liuzzi

> คุณสามารถสร้างแอททริบิวใหม่ด้วยชื่อเดียวกัน (AuthorizeAttribute) ในเนมสเปซเริ่มต้นของเว็บไซต์ของคุณคอมไพเลอร์จะเลือกโดยอัตโนมัติแทน MVC มาตรฐาน ซึ่งส่งผลให้เกิดข้อผิดพลาด: ไม่พบประเภทหรือเนมสเปซ 'อนุญาต' (คุณไม่มีคำสั่งหรือการอ้างอิงแอสเซมบลีหรือไม่) ทั้งคู่ใช้ System.Web.Mvc; และเนมสเปซสำหรับคลาส AuthorizeAttribute ที่กำหนดเองของฉันถูกอ้างอิงในคอนโทรลเลอร์ เพื่อแก้ปัญหานี้ฉันต้องใช้ [MyNamepace.Authorize]
stormwild

2
@DePeter ข้อมูลจำเพาะไม่เคยพูดอะไรเกี่ยวกับการเปลี่ยนเส้นทางดังนั้นทำไมการเปลี่ยนเส้นทางจึงเป็นทางออกที่ดีกว่า เพียงอย่างเดียวนี้ฆ่าอาแจ็กซ์ร้องขอโดยไม่ต้องแฮ็คเพื่อแก้ปัญหา
Adam Tuliper - MSFT

1
ที่ควรเข้าสู่ระบบใน MS Connect เพราะเห็นได้ชัดว่าเป็นข้อผิดพลาดพฤติกรรม ขอบคุณ
Tony Wall

BTW ทำไมเราจึงเปลี่ยนเส้นทางไปที่หน้าเข้าสู่ระบบ? ทำไมไม่ส่งออกรหัส 401 และหน้าเข้าสู่ระบบโดยตรงภายในคำขอเดียวกัน
SandRock

25

เพิ่มไปยังฟังก์ชันเข้าสู่ระบบ Page_Load ของคุณ:

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

เมื่อผู้ใช้ถูกเปลี่ยนเส้นทางที่นั่น แต่เข้าสู่ระบบแล้วมันจะแสดงหน้าเว็บที่ไม่ได้รับอนุญาต หากพวกเขาไม่ได้เข้าสู่ระบบก็จะผ่านและแสดงหน้าเข้าสู่ระบบ


18
Page_Load เป็นเว็บฟอร์ม mojo
โอกาส

2
@Chance - จากนั้นทำใน ActionMethod เริ่มต้นสำหรับคอนโทรลเลอร์ที่เรียกว่า FormsAuthencation ได้รับการตั้งค่าให้เรียก
Pure.Krome

ใช้งานได้ดีจริง ๆ แต่สำหรับ MVC ควรเป็นชื่อif (User.Identity != null && User.Identity.IsAuthenticated) return RedirectToRoute("Unauthorized");ที่ไม่ได้รับอนุญาตเป็นชื่อเส้นทางที่กำหนด
Moses Machua

ดังนั้นคุณถามทรัพยากรคุณจะได้รับการเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบและคุณได้รับการเปลี่ยนเส้นทางอีกครั้งไปที่หน้า 403? ดูเหมือนจะไม่ดีสำหรับฉัน ฉันไม่สามารถทนต่อการเปลี่ยนเส้นทางได้เลย IMO สิ่งนี้สร้างขึ้นไม่ดีมากอยู่ดี
SandRock

3
ตามวิธีการแก้ปัญหาของคุณหากคุณได้เข้าสู่ระบบแล้วไปที่หน้าเข้าสู่ระบบโดยพิมพ์ URL ... สิ่งนี้จะพาคุณไปยังหน้าไม่ได้รับอนุญาต ซึ่งไม่ถูกต้อง
Rajshekar Reddy

4

ฉันคิดเสมอว่านี่สมเหตุสมผลแล้ว หากคุณเข้าสู่ระบบและคุณพยายามที่จะตีหน้าที่ต้องการบทบาทที่ไม่มีคุณจะถูกส่งต่อไปยังหน้าจอเข้าสู่ระบบขอให้คุณเข้าสู่ระบบด้วยผู้ใช้ที่มีบทบาท

คุณอาจเพิ่มตรรกะในหน้าเข้าสู่ระบบที่ตรวจสอบเพื่อดูว่าผู้ใช้รับรองความถูกต้องแล้ว คุณสามารถเพิ่มข้อความที่เป็นมิตรซึ่งอธิบายว่าทำไมพวกเขาจึงกลับมาที่นี่อีกครั้ง


4
ฉันรู้สึกว่าคนส่วนใหญ่มักจะไม่มีตัวตนมากกว่าหนึ่งตัวสำหรับเว็บแอปที่กำหนด ถ้าเป็นเช่นนั้นพวกเขาฉลาดพอที่จะคิดว่า "ID ปัจจุบันของฉันไม่มีโมโจฉันจะลงชื่อเข้าใช้ด้วยชื่ออื่น"
Roger Lipscombe

แม้ว่าจุดอื่นของคุณเกี่ยวกับการแสดงบางอย่างในหน้าเข้าสู่ระบบเป็นสิ่งที่ดี ขอบคุณ
Roger Lipscombe

4

น่าเสียดายที่คุณกำลังจัดการกับพฤติกรรมเริ่มต้นของการตรวจสอบสิทธิ์แบบฟอร์ม ASP.NET มีวิธีแก้ปัญหา (ฉันยังไม่ได้ลอง) อธิบายไว้ที่นี่:

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

(ไม่เฉพาะเจาะจงกับ MVC)

ฉันคิดว่าในกรณีส่วนใหญ่ทางออกที่ดีที่สุดคือ จำกัด การเข้าถึงทรัพยากรที่ไม่ได้รับอนุญาตก่อนที่ผู้ใช้จะพยายามเข้าถึง โดยการลบ / ทำให้เป็นสีเทาลิงค์หรือปุ่มที่อาจนำพวกเขาไปยังหน้าไม่ได้รับอนุญาตนี้

อาจเป็นการดีที่จะมีพารามิเตอร์เพิ่มเติมในแอตทริบิวต์เพื่อระบุตำแหน่งที่จะเปลี่ยนเส้นทางผู้ใช้ที่ไม่ได้รับอนุญาต แต่ในระหว่างนี้ฉันดูที่ AuthorizeAttribute เป็นตาข่ายนิรภัย


ฉันวางแผนที่จะลบลิงก์ตามการอนุญาตเช่นกัน (ฉันเห็นคำถามที่นี่เกี่ยวกับที่ใดที่หนึ่ง) ดังนั้นฉันจะโค้ดวิธีส่วนขยาย HtmlHelper ขึ้นในภายหลัง
Roger Lipscombe

1
ฉันยังคงต้องป้องกันไม่ให้ผู้ใช้ไปยัง URL โดยตรงซึ่งเป็นสิ่งที่คุณสมบัตินี้เกี่ยวกับ ฉันไม่ค่อยมีความสุขกับ Custom 401 solution (ดูเหมือนจะเป็นโลกกว้าง) ดังนั้นฉันจะลองทำแบบจำลองของฉัน NotAuthorizedResult บน RedirectToRouteResult ...
Roger Lipscombe

0

ลองใช้วิธีนี้ในตัวจัดการ Application_EndRequest ของไฟล์ Global.ascx ของคุณ

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

0

หากคุณใช้ aspnetcore 2.0 ให้ใช้สิ่งนี้:

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

0

ในกรณีของฉันปัญหาคือ "ข้อมูลจำเพาะ HTTP ที่ใช้รหัสสถานะ 401 ทั้ง" ไม่ได้รับอนุญาต "และ" ไม่รับรองความถูกต้อง "" ดังที่ ShadowChaser กล่าว

วิธีนี้ใช้ได้ผลสำหรับฉัน:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.