Crucé esta pregunta y espero que mi solución pueda ayudar a alguien.
Tenemos algunos problemas: - Necesitamos asegurar acciones específicas, por ejemplo, "Iniciar sesión" en "Cuenta". Podemos usar la compilación en el atributo RequireHttps, lo cual es genial, pero nos redirigirá con https: //. - Debemos hacer que nuestros enlaces, formularios y tales "sean compatibles con SSL".
Generalmente, mi solución permite especificar rutas que usarán una URL absoluta, además de la capacidad de especificar el protocolo. Puede utilizar este enfoque para especificar el protocolo "https".
Entonces, en primer lugar, he creado una enumeración ConnectionProtocol:
public enum ConnectionProtocol
{
Ignore,
Http,
Https
}
Ahora, he creado una versión enrollada a mano de RequireSsl. He modificado el código fuente de RequireSsl original para permitir la redirección a http: // urls. Además, he puesto un campo que nos permite determinar si debemos requerir SSL o no (lo estoy usando con el preprocesador DEBUG).
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireHttpsAttribute()
{
Protocol = ConnectionProtocol.Ignore;
}
public ConnectionProtocol Protocol { get; set; }
public bool SecureConnectionsAllowed
{
get
{
#if DEBUG
return false;
#else
return true;
#endif
}
}
public void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!SecureConnectionsAllowed)
return;
switch (Protocol)
{
case ConnectionProtocol.Https:
if (!filterContext.HttpContext.Request.IsCurrentConnectionSecured())
{
HandleNonHttpsRequest(filterContext);
}
break;
case ConnectionProtocol.Http:
if (filterContext.HttpContext.Request.IsCurrentConnectionSecured())
{
HandleNonHttpRequest(filterContext);
}
break;
}
}
private void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
private void HandleNonHttpRequest(AuthorizationContext filterContext)
{
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed without SSL.");
}
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Ahora, este RequireSsl hará la siguiente base en el valor de su atributo de Requisitos: - Ignorar: no hará nada. - Http: forzará la redirección al protocolo http. - Https: forzará la redirección al protocolo https.
Debe crear su propio controlador base y establecer este atributo en Http.
[RequireSsl(Requirement = ConnectionProtocol.Http)]
public class MyController : Controller
{
public MyController() { }
}
Ahora, en cada cpntroller / action que le gustaría requerir SSL, simplemente configure este atributo con ConnectionProtocol.Https.
Ahora pasemos a las URL: tenemos algunos problemas con el motor de enrutamiento de URL. Puede leer más sobre ellos en http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . La solución sugerida en esta publicación es teóricamente buena, pero vieja y no me gusta el enfoque.
Mis soluciones son las siguientes: Cree una subclase de la clase básica "Ruta":
clase pública AbsoluteUrlRoute: Ruta {#region ctor
public AbsoluteUrlRoute(string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
}
public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
: base(url, defaults, routeHandler)
{
}
public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,
IRouteHandler routeHandler)
: base(url, defaults, constraints, routeHandler)
{
}
public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,
RouteValueDictionary dataTokens, IRouteHandler routeHandler)
: base(url, defaults, constraints, dataTokens, routeHandler)
{
}
#endregion
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var virtualPath = base.GetVirtualPath(requestContext, values);
if (virtualPath != null)
{
var scheme = "http";
if (this.DataTokens != null && (string)this.DataTokens["scheme"] != string.Empty)
{
scheme = (string) this.DataTokens["scheme"];
}
virtualPath.VirtualPath = MakeAbsoluteUrl(requestContext, virtualPath.VirtualPath, scheme);
return virtualPath;
}
return null;
}
#region Helpers
private string MakeAbsoluteUrl(RequestContext requestContext, string virtualPath, string scheme)
{
return string.Format("{0}://{1}{2}{3}{4}",
scheme,
requestContext.HttpContext.Request.Url.Host,
requestContext.HttpContext.Request.ApplicationPath,
requestContext.HttpContext.Request.ApplicationPath.EndsWith("/") ? "" : "/",
virtualPath);
}
#endregion
}
Esta versión de la clase "Ruta" creará una URL absoluta. El truco aquí, seguido de la sugerencia del autor de la publicación del blog, es usar DataToken para especificar el esquema (ejemplo al final :)).
Ahora, si generamos una URL, por ejemplo, para la ruta "Account / LogOn" obtendremos "/ http://example.com/Account/LogOn ", ya que UrlRoutingModule ve todas las URL como relativas. Podemos arreglar eso usando HttpModule personalizado:
public class AbsoluteUrlRoutingModule : UrlRoutingModule
{
protected override void Init(System.Web.HttpApplication application)
{
application.PostMapRequestHandler += application_PostMapRequestHandler;
base.Init(application);
}
protected void application_PostMapRequestHandler(object sender, EventArgs e)
{
var wrapper = new AbsoluteUrlAwareHttpContextWrapper(((HttpApplication)sender).Context);
}
public override void PostResolveRequestCache(HttpContextBase context)
{
base.PostResolveRequestCache(new AbsoluteUrlAwareHttpContextWrapper(HttpContext.Current));
}
private class AbsoluteUrlAwareHttpContextWrapper : HttpContextWrapper
{
private readonly HttpContext _context;
private HttpResponseBase _response = null;
public AbsoluteUrlAwareHttpContextWrapper(HttpContext context)
: base(context)
{
this._context = context;
}
public override HttpResponseBase Response
{
get
{
return _response ??
(_response =
new AbsoluteUrlAwareHttpResponseWrapper(_context.Response));
}
}
private class AbsoluteUrlAwareHttpResponseWrapper : HttpResponseWrapper
{
public AbsoluteUrlAwareHttpResponseWrapper(HttpResponse response)
: base(response)
{
}
public override string ApplyAppPathModifier(string virtualPath)
{
int length = virtualPath.Length;
if (length > 7 && virtualPath.Substring(0, 7) == "/http:/")
return virtualPath.Substring(1);
else if (length > 8 && virtualPath.Substring(0, 8) == "/https:/")
return virtualPath.Substring(1);
return base.ApplyAppPathModifier(virtualPath);
}
}
}
}
Dado que este módulo anula la implementación base de UrlRoutingModule, deberíamos eliminar el httpModule base y registrar el nuestro en web.config. Entonces, en "system.web", establezca:
<httpModules>
<remove name="UrlRoutingModule-4.0" />
<add name="UrlRoutingModule-4.0" type="MyApp.Web.Mvc.Routing.AbsoluteUrlRoutingModule" />
</httpModules>
Eso es :).
Para registrar una ruta absoluta / seguida por protocolo, debe hacer:
routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}),
DataTokens = new RouteValueDictionary(new {scheme = "https"})
});
Me encantará escuchar sus comentarios y mejoras. ¡Espero que pueda ayudar! :)
Editar: Olvidé incluir el método de extensión IsCurrentConnectionSecured () (demasiados fragmentos: P). Este es un método de extensión que generalmente usa Request.IsSecuredConnection. Sin embargo, este enfoque no funcionará cuando se use el equilibrio de carga, por lo que este método puede omitirlo (tomado de nopCommerce).
public static bool IsCurrentConnectionSecured(this HttpRequestBase request)
{
return request != null && request.IsSecureConnection;
}