Mengapa AuthorizeAttribute mengalihkan ke halaman login untuk kegagalan otentikasi dan otorisasi?

265

Di ASP.NET MVC, Anda dapat menandai metode pengontrol dengan AuthorizeAttribute, seperti ini:

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

Ini berarti bahwa, jika pengguna yang saat ini masuk tidak dalam peran "CanDeleteTags", metode pengontrol tidak akan pernah dipanggil.

Sayangnya, untuk kegagalan, AuthorizeAttributekembali HttpUnauthorizedResult, yang selalu mengembalikan kode status HTTP 401. Ini menyebabkan pengalihan ke halaman login.

Jika pengguna tidak masuk, ini masuk akal. Namun, jika pengguna sudah masuk, tetapi tidak dalam peran yang diperlukan, itu membingungkan untuk mengirim mereka kembali ke halaman login.

Tampaknya AuthorizeAttributemengonfigurasi otentikasi dan otorisasi.

Ini sepertinya sedikit kekeliruan dalam ASP.NET MVC, atau apakah saya melewatkan sesuatu?

Saya harus memasak DemandRoleAttributeyang memisahkan keduanya. Ketika pengguna tidak diautentikasi, ia mengembalikan HTTP 401, mengirimkannya ke halaman login. Ketika pengguna masuk, tetapi tidak dalam peran yang diperlukan, itu NotAuthorizedResultmalah menciptakan . Saat ini ini diarahkan ke halaman kesalahan.

Tentunya saya tidak perlu melakukan ini?

Roger Lipscombe
sumber
10
Pertanyaan yang sangat bagus dan saya setuju, itu harus membuang status HTTP Tidak Diotorisasi.
Pure.Krome
3
Saya suka solusi Anda, Roger. Bahkan jika tidak.
Jon Davis
Halaman Login saya memiliki tanda centang untuk hanya mengarahkan pengguna ke ReturnUrl, jika dia sudah diautentikasi. Jadi saya berhasil membuat loop tak terbatas dari 302 redirect: D woot.
juhan_h
1
Lihat ini .
Jogi
Roger, artikel bagus tentang solusi Anda - red-gate.com/simple-talk/dotnet/asp-net/… Tampaknya solusi Anda adalah satu-satunya cara untuk melakukan ini dengan bersih
Craig

Jawaban:

305

Ketika pertama kali dikembangkan, System.Web.Mvc.AuthorizeAttribute melakukan hal yang benar - revisi yang lebih lama dari spesifikasi HTTP menggunakan kode status 401 untuk "tidak sah" dan "tidak diauthentikasi".

Dari spesifikasi asli:

Jika permintaan sudah menyertakan kredensial Otorisasi, maka respons 401 menunjukkan bahwa otorisasi telah ditolak untuk kredensial tersebut.

Bahkan, Anda dapat melihat kebingungan di sana - itu menggunakan kata "otorisasi" ketika itu berarti "otentikasi". Namun, dalam praktik sehari-hari, lebih masuk akal untuk mengembalikan 403 Forbidden ketika pengguna diautentikasi tetapi tidak diotorisasi. Tidak mungkin pengguna akan memiliki set kredensial kedua yang akan memberi mereka akses - pengalaman pengguna yang buruk di sekitar.

Pertimbangkan sebagian besar sistem operasi - ketika Anda mencoba membaca file yang tidak memiliki izin untuk diakses, Anda tidak diperlihatkan layar login!

Untungnya, spesifikasi HTTP telah diperbarui (Juni 2014) untuk menghapus ambiguitas.

Dari "Protokol Transport Teks Hiper (HTTP / 1.1): Otentikasi" (RFC 7235):

Kode status 401 (Tidak Diotorisasi) menunjukkan bahwa permintaan belum diterapkan karena tidak memiliki kredensial otentikasi yang valid untuk sumber daya target.

Dari "Protokol Transfer Hiperteks (HTTP / 1.1): Semantik dan Konten" (RFC 7231):

Kode status 403 (Terlarang) menunjukkan bahwa server memahami permintaan tersebut tetapi menolak untuk mengesahkannya.

Cukup menarik, pada saat ASP.NET MVC 1 dirilis perilaku AuthorizeAttribute benar. Sekarang, perilaku salah - spesifikasi HTTP / 1.1 telah diperbaiki.

Daripada mencoba mengubah pengalihan halaman login ASP.NET, lebih mudah hanya untuk memperbaiki masalah di sumbernya. Anda dapat membuat atribut baru dengan nama yang sama ( AuthorizeAttribute) di namespace default situs web Anda (ini sangat penting) maka kompiler akan secara otomatis mengambilnya alih-alih yang standar MVC. Tentu saja, Anda selalu bisa memberi atribut nama baru jika Anda lebih suka mengambil pendekatan itu.

[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);
        }
    }
}
ShadowChaser
sumber
52
+1 Pendekatan yang sangat bagus. Saran kecil: alih-alih memeriksa filterContext.HttpContext.User.Identity.IsAuthenticated, Anda hanya dapat memeriksa filterContext.HttpContext.Request.IsAuthenticated, yang disertai dengan pemeriksaan nol bawaan. Lihat stackoverflow.com/questions/1379566/…
Daniel Liuzzi
> Anda dapat membuat atribut baru dengan nama yang sama (AuthorizeAttribute) di namespace default situs web Anda, maka kompiler akan secara otomatis mengambilnya alih-alih yang standar MVC. Ini menghasilkan kesalahan: Jenis atau namespace 'Otorisasi' tidak dapat ditemukan (apakah Anda kehilangan arahan atau referensi perakitan?) Keduanya menggunakan System.Web.Mvc; dan namespace untuk kelas AuthorizeAttribute kustom saya direferensikan di controller. Untuk mengatasi ini saya harus menggunakan [MyNamepace.Authorize]
stormwild
2
@DePeter spec tidak pernah mengatakan apa pun tentang pengalihan jadi mengapa pengalihan merupakan solusi yang lebih baik? Ini saja membunuh permintaan ajax tanpa hack di tempat untuk menyelesaikannya.
Adam Tuliper - MSFT
1
Itu harus dicatat di MS Connect karena ini jelas merupakan bug perilaku. Terima kasih.
Tony Wall
BTW, mengapa kita dialihkan ke halaman login? Mengapa tidak hanya mengeluarkan kode 401 dan halaman login langsung dalam permintaan yang sama?
SandRock
25

Tambahkan ini ke fungsi Halaman Login Anda:

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

Ketika pengguna dialihkan ke sana tetapi sudah masuk, itu menunjukkan halaman yang tidak sah. Jika mereka tidak masuk, itu masuk dan menunjukkan halaman login.

Alan Jackson
sumber
18
Page_Load adalah moform webforma
Peluang
2
@ Peluang - kemudian lakukan itu di ActionMethod default untuk controller yang dipanggil di mana FormsAuthencation telah diatur untuk memanggil.
Pure.Krome
Ini benar-benar berfungsi dengan baik meskipun untuk MVC itu harus seperti di if (User.Identity != null && User.Identity.IsAuthenticated) return RedirectToRoute("Unauthorized");mana Unauthorized adalah nama rute yang ditentukan.
Moses Machua
Jadi, Anda bertanya sumber daya, Anda dialihkan ke halaman login dan Anda diarahkan kembali ke halaman 403? Tampak buruk bagi saya. Saya bahkan tidak bisa mentolerir satu pengalihan sama sekali. IMO hal ini dibangun dengan sangat buruk.
SandRock
3
Menurut solusi Anda, Jika Anda telah masuk dan masuk ke halaman Login dengan mengetikkan URL ... ini akan membawa Anda ke halaman Tidak Sah. mana yang tidak benar.
Rajshekar Reddy
4

Saya selalu berpikir ini masuk akal. Jika Anda masuk dan mencoba membuka halaman yang membutuhkan peran yang tidak Anda miliki, Anda akan diteruskan ke layar login meminta Anda untuk masuk dengan pengguna yang memang memiliki peran tersebut.

Anda dapat menambahkan logika ke halaman login yang memeriksa untuk melihat apakah pengguna sudah dikonfirmasi. Anda dapat menambahkan pesan ramah yang menjelaskan mengapa mereka telah dibohongi kembali di sana.

rampok
sumber
4
Menurut perasaan saya, kebanyakan orang cenderung tidak memiliki lebih dari satu identitas untuk aplikasi web tertentu. Jika mereka melakukannya, maka mereka cukup pintar untuk berpikir "ID saya saat ini tidak memiliki mojo, saya akan masuk kembali seperti yang lain".
Roger Lipscombe
Meskipun poin Anda yang lain tentang menampilkan sesuatu pada halaman login adalah bagus. Terima kasih.
Roger Lipscombe
4

Sayangnya, Anda berurusan dengan perilaku default otentikasi formulir ASP.NET. Ada solusi (saya belum mencobanya) yang dibahas di sini:

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

(Ini tidak khusus untuk MVC)

Saya pikir dalam banyak kasus solusi terbaik adalah membatasi akses ke sumber daya yang tidak sah sebelum pengguna mencoba untuk sampai ke sana. Dengan menghapus / memutari tautan atau tombol yang mungkin membawa mereka ke halaman tidak sah ini.

Mungkin akan menyenangkan untuk memiliki parameter tambahan pada atribut untuk menentukan di mana mengarahkan pengguna yang tidak sah. Tetapi sementara itu, saya melihat AuthorizeAttribute sebagai jaring pengaman.

Keltex
sumber
Saya berencana untuk menghapus tautan berdasarkan otorisasi juga (saya melihat pertanyaan di sini tentang itu di suatu tempat), jadi saya akan mengkode metode ekstensi HtmlHelper nanti.
Roger Lipscombe
1
Saya masih harus mencegah pengguna untuk langsung menuju URL, yang merupakan atribut dari semua ini. Saya tidak terlalu senang dengan solusi Custom 401 (sepertinya sedikit global), jadi saya akan mencoba memodelkan NotAuthorizedResult saya di RedirectToRouteResult ...
Roger Lipscombe
0

Coba ini di dalam Anda di Application_EndRequest handler file Global.ascx Anda

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

Jika Anda menggunakan aspnetcore 2.0, gunakan ini:

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;
            }
        }
    }
}
Greg Gum
sumber
0

Dalam kasus saya masalahnya adalah "spesifikasi HTTP menggunakan kode status 401 untuk" tidak sah "dan" tidak diauthentikasi "". Seperti yang dikatakan ShadowChaser.

Solusi ini bekerja untuk saya:

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" });
}
César León
sumber