ASP.NET MVC RequireHttps dalam Produksi Saja

121

Saya ingin menggunakan RequireHttpsAttribute untuk mencegah permintaan HTTP yang tidak aman dikirim ke metode tindakan.

C #

[RequireHttps] //apply to all actions in controller
public class SomeController 
{
    [RequireHttps] //apply to this action only
    public ActionResult SomeAction()
    {
        ...
    }
}

VB

<RequireHttps()> _
Public Class SomeController

    <RequireHttps()> _
    Public Function SomeAction() As ActionResult
        ...
    End Function

End Class

Sayangnya, Server Pengembangan ASP.NET tidak mendukung HTTPS.

Bagaimana cara membuat aplikasi ASP.NET MVC saya menggunakan RequireHttps saat diterbitkan ke lingkungan produksi, tetapi tidak saat dijalankan di workstation pengembangan saya di Server Pengembangan ASP.NET?

Zack Peterson
sumber
3
Uji dengan IIS lokal Anda dan dengan IIS Express. Lihat saya SSL blog blogs.msdn.com/b/rickandy/archive/2011/04/22/... dan blogs.msdn.com/b/rickandy/archive/2012/03/23/...
RickAndMSFT

Jawaban:

129

Ini tidak akan membantu jika Anda menjalankan Rilis build pada workstation pengembangan Anda, tetapi kompilasi bersyarat dapat melakukan pekerjaan itu ...

#if !DEBUG
[RequireHttps] //apply to all actions in controller
#endif
public class SomeController 
{
    //... or ...
#if !DEBUG
    [RequireHttps] //apply to this action only
#endif
    public ActionResult SomeAction()
    {
    }

}

Memperbarui

Dalam Visual Basic, atribut secara teknis merupakan bagian dari baris yang sama dengan definisi yang mereka terapkan. Anda tidak bisa meletakkan pernyataan kompilasi bersyarat di dalam baris, jadi Anda dipaksa untuk menulis deklarasi fungsi dua kali - sekali dengan atribut, dan sekali tanpa. Itu berhasil, jika Anda tidak keberatan dengan keburukannya.

#If Not Debug Then
    <RequireHttps()> _
    Function SomeAction() As ActionResult
#Else
    Function SomeAction() As ActionResult
#End If
        ...
    End Function

Perbarui 2

Beberapa orang telah menyebutkan berasal dari RequireHttpsAttributetanpa memberikan contoh, jadi inilah satu untuk Anda. Saya pikir pendekatan ini akan jauh lebih bersih daripada pendekatan kompilasi bersyarat, dan itu akan menjadi preferensi saya pada posisi Anda.

DISCLAIMER: Saya belum menguji kode ini, bahkan sedikit, dan VB saya cukup berkarat. Yang saya tahu adalah bahwa itu terkompilasi. Saya menulisnya berdasarkan saran dari spot, queen3, dan Lance Fisher. Jika tidak berhasil, setidaknya itu harus menyampaikan gagasan umum, dan memberi Anda titik awal.

Public Class RemoteRequireHttpsAttribute
    Inherits System.Web.Mvc.RequireHttpsAttribute

    Public Overrides Sub OnAuthorization(ByVal filterContext As  _
                                         System.Web.Mvc.AuthorizationContext)
        If IsNothing(filterContext) Then
            Throw New ArgumentNullException("filterContext")
        End If

        If Not IsNothing(filterContext.HttpContext) AndAlso _
            filterContext.HttpContext.Request.IsLocal Then
            Return
        End If

        MyBase.OnAuthorization(filterContext)
    End Sub

End Class

Pada dasarnya, atribut baru keluar begitu saja alih-alih menjalankan kode otorisasi SSL default, jika permintaan saat ini bersifat lokal (yaitu, Anda mengakses situs melalui localhost). Anda bisa menggunakannya seperti ini:

<RemoteRequireHttps()> _
Public Class SomeController

    <RemoteRequireHttps()> _
    Public Function SomeAction() As ActionResult
        ...
    End Function

End Class

Jauh lebih bersih! Asalkan kode saya yang belum diuji benar-benar berfungsi.

Joel Mueller
sumber
Terima kasih untuk, um, mengedit posting saya untuk saya, Zack. Pertanyaan Anda ada di C #, jadi saya memposting tanggapan C #. Saya tidak tahu VB relevan. Ada yang tahu jika ada cara untuk menggunakan kompilasi bersyarat untuk mengontrol atribut di VB, atau apakah itu tidak mungkin?
Joel Mueller
Ya, ini berfungsi untuk C #, dan juga berfungsi untuk VB, tetapi Anda harus melakukan duplikasi yang agak jelek dari definisi fungsi / kelas. Lihat jawaban terbaru saya di atas.
Joel Mueller
Maaf. Sampel kode VB semakin sulit didapat. Saya tidak berpikir itu akan menjadi masalah. Saya telah memperbarui pertanyaan asli. Apakah kompilasi bersyarat di sekitar atribut bekerja dengan pasti di C #? Saya belum menguji. Tampaknya itu solusi yang sempurna dan elegan.
Zack Peterson
Kode RemoteRequireHttpsAttribute Anda bekerja dengan sempurna. Itu jauh lebih elegan untuk VB daripada kompilasi bersyarat. Terima kasih lagi Joel.
Zack Peterson
2
Terima kasih-- inilah yang saya butuhkan. Bersulang!
davecoulter
65

Jika ada yang membutuhkan versi C #:

using System;
using System.Web.Mvc;

namespace My.Utils
{
    public class MyRequireHttpsAttribute : RequireHttpsAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (filterContext.HttpContext != null && filterContext.HttpContext.Request.IsLocal)
            {
                return;
            }

            base.OnAuthorization(filterContext);
        }
    }
}
mikesl
sumber
ok ketika membaca ini dan ini sebagai ukuran keamanan harus kita tambahkan filters.Add(new MyRequireHttpsAttribute ());di FilterConfig?
shaijut
Berdasarkan jawaban ini saya membuat solusi untuk MVC 6 menggunakan filter di Startup.cs atau gaya atribut pada Controller.
Nick Niebling
26

Berasal dari RequireHttps adalah pendekatan yang baik.

Untuk mengatasi masalah sepenuhnya, Anda dapat menggunakan IIS di komputer lokal Anda dengan sertifikat yang ditandatangani sendiri juga. IIS lebih cepat daripada server web internal, dan Anda memiliki keuntungan bahwa lingkungan pengembangan Anda lebih seperti produksi.

Scott Hanselman memiliki sumber daya yang bagus tentang beberapa cara untuk mengimplementasikan HTTPS lokal dengan VS2010 dan IIS Express.

Lance Fisher
sumber
ya - sampai Anda mencoba melakukan port forwarding dengan perangkat Mifi wifi Verizon dan menemukan bahwa port 443 tidak tersedia untuk diteruskan !!! # * & # * & $
Simon_Weaver
Yang tidak saya sukai tentang menggunakan IIS di komputer lokal Anda dengan sertifikat yang ditandatangani sendiri adalah bahwa saya harus melalui langkah penerapan tambahan untuk menguji perubahan. Menurut saya, jika Anda menguji sesuatu yang terkait dengan keamanan daripada yang masuk akal, tetapi katakan jika Anda hanya memeriksa beberapa perubahan kecil lainnya, itu menyakitkan untuk harus menyebarkan hanya untuk mengatasi ketidakmampuan Cassini untuk mendukung HTTPS.
davecoulter
1
@davecoulter - Gunakan IIS express pada windows versi klien, tidak diperlukan cassini dan akan bekerja persis seperti IIS, termasuk memiliki kemampuan ssl.
Erik Funkenbusch
@Mystere Man - ya, saya memang menemukannya sejak komentar itu. Terima kasih atas tipnya :)
davecoulter
lebih banyak informasi atau tautan harus ditambahkan tentang bagaimana melakukan hal-hal seperti itu.
stephenbayer
12

Memanfaatkan sistem filter MVC dan Global.asax.cs, saya berasumsi Anda bisa melakukan ini ...

    protected void Application_Start()
    {
      RegisterGlobalFilters(GlobalFilters.Filters);
    }

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
      filters.Add(new HandleErrorAttribute());
      if(Config.IsProduction) //Some flag that you can tell if you are in your production environment.
      {
        filters.Add(new RequireHttpsAttribute());
      }
    }
gt124
sumber
Saya lebih suka jawaban ini karena melibatkan satu pemeriksaan per masa aplikasi daripada menerapkan filter baru yang akan dijalankan \ dipanggil dengan setiap permintaan.
Abdulhameed
10

Karena ASP.Net Development Server yang menyebabkan masalah Anda di tempat pertama, perlu dicatat bahwa Microsoft sekarang memiliki IIS Express , yang dikirimkan dengan Visual Studio (sejak VS2010 SP1). Ini adalah versi cut-down IIS yang mudah digunakan seperti Development Server, tetapi mendukung set fitur lengkap IIS 7.5 termasuk SSL.

Scott Hanselman memiliki postingan terperinci tentang bekerja dengan SSL di IIS Express .

Samuel Jack
sumber
9

Bagaimana dengan mewarisi atribut RequireHttps dalam atribut khusus. Kemudian, di dalam atribut khusus Anda, periksa properti IsLocal dari permintaan saat ini untuk melihat apakah permintaan tersebut berasal dari mesin lokal. Jika ya, maka jangan terapkan fungsionalitas dasar. Jika tidak, panggil operasi basis.

titik
sumber
4

Ini berhasil untuk saya, MVC 6 (ASP.NET Core 1.0) . Kode memeriksa apakah debug sedang dalam pengembangan, dan jika tidak, ssl tidak diperlukan. Semua pengeditan ada di Startup.cs .

Menambahkan:

private IHostingEnvironment CurrentEnvironment { get; set; }

Menambahkan:

public Startup(IHostingEnvironment env)
{
    CurrentEnvironment = env;
}

Edit:

public void ConfigureServices(IServiceCollection services)
{
    // additional services...

    services.AddMvc(options =>
    {
        if (!CurrentEnvironment.IsDevelopment())
        {
            options.Filters.Add(typeof(RequireHttpsAttribute));
        }
    });
}
Eric Beijner
sumber
3

Jika Anda dapat memperoleh dan menimpa - lakukanlah. Jika Anda tidak bisa - MVC dilengkapi dengan sumber, ambil saja sumbernya dan buat atribut [ForceHttps] Anda sendiri yang memeriksa IsLocal.

ratu3
sumber
3

Untuk MVC 3 saya menambahkan FilterProvider saya sendiri (berdasarkan kode yang ditemukan di sini: Filter Global dan Bersyarat yang, antara lain (menampilkan info Debug untuk pengguna lokal, dll.) Akan menghiasi semua tindakan dengan RequireHttpsAttributekapan HttpContext.Request.IsLocal == false.

juhan_h
sumber
Atau Anda bisa menambahkannya secara bersyarat ke kumpulan filter global saat permintaan bersifat lokal. Perhatikan bahwa Anda ingin memeriksa ini di blok coba / tangkap jika aplikasi diatur untuk segera dimulai karena permintaan mungkin tidak tersedia.
tvanfosson
3

Setelah meneliti dengan keras, saya dapat memecahkan masalah ini dengan IIS Express dan menimpa metode OnAuthorization kelas Controller (Ref # 1). Saya juga telah mengikuti rute yang direkomendasikan oleh Hanselman (Referensi # 2). Namun, saya tidak sepenuhnya puas dengan dua solusi ini karena dua alasan: 1. OnAuthorization Ref # 1 hanya bekerja pada level tindakan, bukan pada level kelas controller 2. Ref # 2 membutuhkan banyak pengaturan (Win7 SDK untuk makecert ), perintah netsh, dan, untuk menggunakan port 80 dan port 443, saya perlu meluncurkan VS2010 sebagai administrator, yang tidak saya sukai.

Jadi, saya menemukan solusi ini yang berfokus pada kesederhanaan dengan kondisi berikut:

  1. Saya ingin dapat menggunakan attbbute RequireHttps di kelas Controller atau tingkat tindakan

  2. Saya ingin MVC menggunakan HTTPS ketika atribut RequireHttps ada, dan menggunakan HTTP jika tidak ada

  3. Saya tidak ingin menjalankan Visual Studio sebagai administrator

  4. Saya ingin dapat menggunakan port HTTP dan HTTPS apa pun yang ditetapkan oleh IIS Express (Lihat Catatan # 1)

  5. Saya dapat menggunakan kembali sertifikat SSL yang ditandatangani sendiri dari IIS Express, dan saya tidak peduli jika saya melihat prompt SSL yang tidak valid

  6. Saya ingin dev, test, dan produksi memiliki basis kode yang sama persis dan biner yang sama dan independen dari pengaturan tambahan (misalnya menggunakan netsh, mmc cert snap-in, dll.) Sebanyak mungkin

Sekarang, dengan latar belakang dan penjelasannya, saya harap kode ini akan membantu seseorang dan menghemat waktu. Pada dasarnya, buat kelas BaseController yang diturunkan dari Controller, dan turunkan kelas kontroler Anda dari kelas dasar ini. Karena Anda telah membaca sejauh ini, saya berasumsi bahwa Anda tahu cara melakukannya. Selamat membuat kode!

Catatan # 1: Ini dicapai dengan menggunakan fungsi 'getConfig' yang berguna (lihat kode)

Referensi # 1: http://puredotnetcoder.blogspot.com/2011/09/requirehttps-attribute-in-mvc3.html

Ref # 2: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx

========== Kode di BaseController ===================

     #region Override to reroute to non-SSL port if controller action does not have RequireHttps attribute to save on CPU 
    // By L. Keng, 2012/08/27
    // Note that this code works with RequireHttps at the controller class or action level.
    // Credit: Various stackoverflow.com posts and http://puredotnetcoder.blogspot.com/2011/09/requirehttps-attribute-in-mvc3.html
    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        // if the controller class or the action has RequireHttps attribute
        var requireHttps = (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Count() > 0 
                            || filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Count() > 0);
        if (Request.IsSecureConnection)
        {
            // If request has a secure connection but we don't need SSL, and we are not on a child action   
            if (!requireHttps && !filterContext.IsChildAction)
            {
                var uriBuilder = new UriBuilder(Request.Url)
                {
                    Scheme = "http",
                    Port = int.Parse(getConfig("HttpPort", "80")) // grab from config; default to port 80
                };
                filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
            }
        }
        else
        {
            // If request does not have a secure connection but we need SSL, and we are not on a child action   
            if (requireHttps && !filterContext.IsChildAction)
            {
                var uriBuilder = new UriBuilder(Request.Url)
                {
                    Scheme = "https",
                    Port = int.Parse(getConfig("HttpsPort", "443")) // grab from config; default to port 443
                };
                filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
            }
        }
        base.OnAuthorization(filterContext);
    }
    #endregion

    // a useful helper function to get appSettings value; allow caller to specify a default value if one cannot be found
    internal static string getConfig(string name, string defaultValue = null)
    {
        var val = System.Configuration.ConfigurationManager.AppSettings[name];
        return (val == null ? defaultValue : val);
    }

============== kode akhir ================

Di Web.Release.Config, tambahkan berikut ini untuk membersihkan HttpPort dan HttpsPort (untuk menggunakan default 80 dan 443).

<appSettings>
<add key="HttpPort" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
<add key="HttpsPort" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
</appSettings>
Leng Keng
sumber
3

Satu solusi yang dapat Anda gunakan pada produksi dan juga pada workstation pengembangan. Ini berdasarkan pilihan Anda dari pengaturan aplikasi di web.config

<appSettings>
     <!--Use SSL port 44300 in IIS Express on development workstation-->
     <add key="UseSSL" value="44300" />
</appSettings>

Jika Anda tidak ingin menggunakan SSL, hapus kuncinya. Jika Anda menggunakan SSL standar port 443, hapus nilainya atau tentukan 443.

Kemudian gunakan implementasi kustom RequireHttpsAttribute yang menangani kondisi Anda. Ini sebenarnya diturunkan dari RequireHttps dan menggunakan implementasi yang sama dari metode dasar kecuali untuk menambahkan kondisi.

public class RequireHttpsConditional : RequireHttpsAttribute
{
    protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        var useSslConfig = ConfigurationManager.AppSettings["UseSSL"];
        if (useSslConfig != null)
        {
            if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
            }

            var request = filterContext.HttpContext.Request;
            string url = null;
            int sslPort;

            if (Int32.TryParse(useSslConfig, out sslPort) && sslPort > 0)
            {
                url = "https://" + request.Url.Host + request.RawUrl;

                if (sslPort != 443)
                {
                    var builder = new UriBuilder(url) {Port = sslPort};
                    url = builder.Uri.ToString();
                }
            }

            if (sslPort != request.Url.Port)
            {
                filterContext.Result = new RedirectResult(url);
            }
        }
    }
}

Jangan lupa untuk menghias metode LogOn di AccountController

[RequireHttpsConditional]
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)

dan sesuatu seperti ini di Tampilan Logon Anda untuk mengirim formulir melalui https.

<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>
Nick
sumber
saya mendapatkan kesalahan ini: XMLHttpRequest tidak dapat memuat m.XXX.com/Auth/SignIn . Tidak ada header 'Access-Control-Allow-Origin' yang ada di resource yang diminta. Oleh karena itu, asal ' m.XXX.com ' tidak diizinkan mengakses.
Ranjith Kumar Nagiri
2

Seperti yang disebutkan Joel, Anda dapat mengubah kompilasi dengan menggunakan #if !DEBUGdirektif.

Saya baru tahu bahwa Anda dapat mengubah nilai simbol DEBUG di elemen kompilasi file web.config. Semoga membantu.

Jose
sumber
1

MVC 6 (ASP.NET Core 1.0):

Solusi yang tepat adalah menggunakan env.IsProduction () atau env.IsDevelopment (). Baca lebih lanjut tentang alasan di balik jawaban ini tentang cara membutuhkan https hanya dalam produksi .

Jawaban ringkas di bawah (lihat tautan di atas untuk membaca lebih lanjut tentang keputusan desain) untuk 2 gaya berbeda:

  1. Startup.cs - daftar filter
  2. BaseController - gaya atribut

Startup.cs (filter register):

public void ConfigureServices(IServiceCollection services)
{
    // TODO: Register other services

    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(RequireHttpsInProductionAttribute));
    });
}

BaseController.cs (gaya atribut):

[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
    // Maybe you have other shared controller logic..
}

public class HomeController : BaseController
{
    // Add endpoints (GET / POST) for Home controller
}

RequireHttpsInProductionAttribute : Keduanya menggunakan atribut khusus yang diturunkan dari RequireHttpsAttribute :

public class RequireHttpsInProductionAttribute : RequireHttpsAttribute
{
    private bool IsProduction { get; }

    public RequireHttpsInProductionAttribute(IHostingEnvironment environment)
    {
        if (environment == null)
            throw new ArgumentNullException(nameof(environment));
        this.IsProduction = environment.IsProduction(); 
    }
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (this.IsProduction)
            base.OnAuthorization(filterContext);
    }
    protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        if(this.IsProduction)
            base.HandleNonHttpsRequest(filterContext);
    }
}
Nick Niebling
sumber
1

Ini adalah cara terbersih bagiku. Di App_Start\FilterConfig.csfile saya . Tidak dapat menjalankan rilis build lagi.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (!Web.HttpContext.Current.IsDebuggingEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}

Sebagai alternatif, Anda dapat mengaturnya untuk hanya meminta https saat halaman kesalahan kustom Anda aktif.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (Web.HttpContext.Current.IsCustomErrorEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}
Carter Medlin
sumber
Ini adalah solusi mudah yang bekerja dengan baik di MVC 5 :)
MWD