ASP.net MVC RequireHttps somente em produção

121

Quero usar o RequireHttpsAttribute para impedir que solicitações HTTP não seguras sejam enviadas para um método de ação.

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

Infelizmente, o ASP.NET Development Server não oferece suporte a HTTPS.

Como posso fazer meu aplicativo ASP.NET MVC usar RequireHttps quando publicado no ambiente de produção, mas não quando executado na minha estação de trabalho de desenvolvimento no ASP.NET Development Server?

Zack Peterson
fonte
3
Teste com o IIS local e com o IIS Express. Veja o meu blog SSL blogs.msdn.com/b/rickandy/archive/2011/04/22/… e blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT em

Respostas:

129

Isso não ajudará se você executar versões da versão em sua estação de trabalho de desenvolvimento, mas a compilação condicional poderá fazer o trabalho ...

#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()
    {
    }

}

Atualizar

No Visual Basic, os atributos são tecnicamente parte da mesma linha que a definição a que se aplicam. Você não pode colocar instruções de compilação condicional dentro de uma linha; portanto, você é forçado a escrever a declaração da função duas vezes - uma com o atributo e outra sem. Funciona, no entanto, se você não se importa com a feiura.

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

Atualização 2

Várias pessoas mencionaram derivar RequireHttpsAttributesem fornecer um exemplo, então aqui está um para você. Penso que esta abordagem seria muito mais limpa do que a abordagem de compilação condicional e seria a minha preferência na sua posição.

AVISO LEGAL: Eu não testei esse código, nem um pouco, e meu VB está bastante enferrujado. Tudo o que sei é que ele compila. Eu o escrevi com base nas sugestões de spot, queen3 e Lance Fisher. Se não funcionar, deve pelo menos transmitir a ideia geral e fornecer o ponto de partida.

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

Basicamente, o novo atributo é encerrado em vez de executar o código de autorização SSL padrão, se a solicitação atual for local (ou seja, você está acessando o site por meio do host local). Você pode usá-lo assim:

<RemoteRequireHttps()> _
Public Class SomeController

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

End Class

Muito mais limpo! Desde que meu código não testado realmente funcione.

Joel Mueller
fonte
Obrigado por editar meu post, Zack. Sua pergunta estava em C #, então postei uma resposta em C #. Eu não sabia que o VB era relevante. Alguém sabe se existe uma maneira de usar a compilação condicional para controlar atributos no VB, ou isso simplesmente não é possível?
Joel Mueller
Sim, funciona para C # e também para VB, mas você precisa fazer uma duplicação bastante feia da definição de função / classe. Veja minha resposta atualizada acima.
Joel Mueller
Desculpe. Os exemplos de código VB estão ficando cada vez mais difíceis de encontrar. Eu não achei que isso importaria. Atualizei a pergunta original. A compilação condicional em torno dos atributos funciona com certeza em C #? Eu não testei. Parece uma solução perfeita e elegante.
Zack Peterson
Seu código RemoteRequireHttpsAttribute funciona perfeitamente. Isso é muito mais elegante para o VB do que a compilação condicional. Mais uma vez obrigado Joel.
Zack Peterson
2
Obrigado-- era exatamente o que eu precisava. Felicidades!
Davecoulter 5/05
65

Se alguém precisar da versão 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
fonte
ok ao ler este e esta como uma medida de segurança que devemos adicionar filters.Add(new MyRequireHttpsAttribute ());em FilterConfig?
shaijut
Com base nesta resposta, criei uma solução para o MVC 6 usando o filtro no Startup.cs ou o estilo do atributo no Controller.
Nick Niebling
26

Derivar de RequireHttps é uma boa abordagem.

Para solucionar o problema completamente, você também pode usar o IIS em sua máquina local com um certificado autoassinado. O IIS é mais rápido que o servidor da web interno e você tem a vantagem de que seu ambiente de desenvolvimento é mais parecido com a produção.

Scott Hanselman tem um ótimo recurso em algumas maneiras de implementar o HTTPS local com o VS2010 e o IIS Express.

Lance Fisher
fonte
sim - até você tentar fazer o encaminhamento de porta com um dispositivo Mifi wifi Verizon e descobrir que a porta 443 não está disponível para encaminhamento !!! # * & # * & $
Simon_Weaver
O que não gosto em usar o IIS em sua máquina local com um certificado autoassinado é que tenho que passar por uma etapa extra de implantação para testar as alterações. Eu acho que se você está testando algo relacionado à segurança do que faz sentido, mas diga que se você está apenas verificando alguma outra alteração menor, é doloroso ter que implantar apenas para contornar a incapacidade da Cassini de oferecer suporte ao HTTPS.
Davecoulter 5/05
1
@davecoulter - Use o IIS express em versões de cliente do Windows, não é necessário cassini e funcionará exatamente como o IIS, incluindo a capacidade de SSL.
Erik Funkenbusch
@Mystere Man - sim, eu descobri isso desde esse comentário. Obrigado pela dica :)
davecoulter
mais informações ou link devem ser adicionados sobre como proceder para fazer essas coisas.
stephenbayer
12

Aproveitando o sistema de filtro MVC e o Global.asax.cs, suponho que você possa fazer isso ...

    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
fonte
Prefiro essa resposta, pois envolve uma verificação por tempo de vida do aplicativo, em vez de implementar um novo filtro que será executado \ chamado a cada solicitação.
Abdulhameed
10

Como foi o ASP.Net Development Server que causou o seu problema, em primeiro lugar, é importante notar que a Microsoft agora possui o IIS Express , que é fornecido com o Visual Studio (desde o VS2010 SP1). Esta é uma versão reduzida do IIS que é tão fácil de usar quanto o Servidor de Desenvolvimento, mas suporta o conjunto completo de recursos do IIS 7.5, incluindo SSL.

Scott Hanselman tem uma postagem detalhada sobre como trabalhar com SSL no IIS Express .

Samuel Jack
fonte
9

Que tal herdar o atributo RequireHttps em um atributo personalizado. Em seguida, dentro do seu atributo customizado, verifique a propriedade IsLocal da solicitação atual para ver se a solicitação é proveniente da máquina local. Se for, não aplique a funcionalidade básica. Caso contrário, chame a operação base.

local
fonte
4

Isso funcionou para mim, MVC 6 (ASP.NET Core 1.0) . O código verifica se a depuração está em desenvolvimento e, se não, o ssl não é necessário. Todas as edições estão em Startup.cs .

Adicionar:

private IHostingEnvironment CurrentEnvironment { get; set; }

Adicionar:

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

Editar:

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

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

Se você pode derivar e substituir - faça-o. Se você não puder - o MVC vem com fontes, basta pegar as fontes e criar seu próprio atributo [ForceHttps] que verifica IsLocal.

queen3
fonte
3

Para o MVC 3, adicionei meu próprio FilterProvider (com base no código encontrado aqui: Filtros Globais e Condicionais que, entre outras coisas (exibindo informações de Depuração para usuários locais etc.), decoram todas as ações com RequireHttpsAttributequando HttpContext.Request.IsLocal == false.

juhan_h
fonte
Ou você pode apenas adicionar condicionalmente à coleção de filtros globais quando a solicitação for local. Observe que você deseja verificar isso em um bloco de tentativa / captura, se o aplicativo estiver configurado para iniciar imediatamente, pois a solicitação pode não estar disponível.
precisa saber é o seguinte
3

Após pesquisar em voz alta, consegui resolver esse problema com o IIS Express e uma substituição do método OnAuthorization da classe Controller (Ref # 1). Eu também segui a rota recomendada por Hanselman (Ref # 2). No entanto, eu não estava completamente satisfeito com essas duas soluções devido a dois motivos: 1. A OnAuthorization da referência nº 1 funciona apenas no nível da ação, não na classe da controladora 2. A referência nº 2 requer muita configuração (Win7 SDK for makecert ), comandos netsh e, para usar a porta 80 e a porta 443, preciso iniciar o VS2010 como administrador, com o qual desaprovo.

Então, eu vim com esta solução que se concentra na simplicidade com as seguintes condições:

  1. Quero poder usar o attbbute RequireHttps na classe ou nível de ação do Controller

  2. Quero que o MVC use HTTPS quando o atributo RequireHttps estiver presente e use HTTP se ele estiver ausente

  3. Não quero ter que executar o Visual Studio como administrador

  4. Quero poder usar as portas HTTP e HTTPS atribuídas pelo IIS Express (consulte a Nota 1)

  5. Posso reutilizar o certificado SSL autoassinado do IIS Express e não me importo se vir o prompt SSL inválido

  6. Desejo que o desenvolvedor, o teste e a produção tenham exatamente a mesma base de código e o mesmo binário, além de serem independentes de configurações adicionais (por exemplo, usando netsh, snap-in de certificação mmc etc.)

Agora, com o plano de fundo e a explicação fora do caminho, espero que esse código ajude alguém e economize algum tempo. Basicamente, crie uma classe BaseController que herda do Controller e obtenha suas classes de controlador dessa classe base. Desde que você leu até aqui, presumo que você saiba como fazer isso. Então, feliz codificação!

Nota # 1: Isso é alcançado pelo uso de uma função útil 'getConfig' (consulte o código)

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

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

========== Código no 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);
    }

============== código final ================

No Web.Release.Config, adicione o seguinte para limpar o HttpPort e o HttpsPort (para usar o padrão 80 e 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
fonte
3

Uma solução que você pode usar na produção e na estação de trabalho de desenvolvimento. É baseado em sua opção nas configurações do aplicativo em web.config

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

Se você não quiser usar SSL, remova a chave. Se você usar a porta SSL padrão 443, remova o valor ou especifique 443.

Em seguida, use a implementação customizada de RequireHttpsAttribute que cuida de sua condição. Na verdade, é derivado de RequireHttps e usa a mesma implementação do método base, exceto para adicionar condições.

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

Não se esqueça de decorar o método LogOn no AccountController

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

e algo assim na sua exibição de logon para postar o formulário em https.

<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>
usuario
fonte
Estou recebendo este erro: XMLHttpRequest não pode carregar m.XXX.com/Auth/SignIn . Nenhum cabeçalho 'Access-Control-Allow-Origin' está presente no recurso solicitado. A origem ' m.XXX.com ' não é, portanto, permitida.
Ranjith Kumar Nagiri
2

Como Joel mencionou, você pode alterar a compilação usando a #if !DEBUGdiretiva.

Acabei de descobrir que você pode alterar o valor do símbolo DEBUG no elemento de compilação do arquivo web.config. Espero que ajude.

Jose
fonte
1

MVC 6 (ASP.NET Core 1.0):

A solução adequada seria usar env.IsProduction () ou env.IsDevelopment (). Leia mais sobre o motivo por trás desta resposta sobre como exigir https somente na produção .

Resposta condensada abaixo (veja o link acima para ler mais sobre decisões de design) para 2 estilos diferentes:

  1. Startup.cs - filtro de registro
  2. BaseController - estilo de atributo

Startup.cs (filtro de registro):

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

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

BaseController.cs (estilo do atributo):

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

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

RequireHttpsInProductionAttribute : acima estão usando o atributo personalizado herdado de 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
fonte
1

Esta foi a maneira mais limpa para mim. No meu App_Start\FilterConfig.csarquivo No entanto, não é possível executar versões de lançamento.

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

Como alternativa, você pode configurá-lo para exigir apenas https quando sua página de erro personalizada estiver ativada.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (Web.HttpContext.Current.IsCustomErrorEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}
Carter Medlin
fonte
Esta é uma solução fácil que funciona muito bem em MVC 5 :)
MWD