Сторінки SSL під ASP.NET MVC


80

Як мені почати використовувати HTTPS для деяких сторінок мого веб-сайту на основі ASP.NET MVC?

Стів Сандерсон має досить непоганий підручник про те, як це зробити СУХИМ способом у Preview 4 на:

http://blog.codeville.net/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/

Чи є кращий / оновлений спосіб із Preview 5 ?,


3
Це дуже датовано. Щодо MVC4 та новіших версій, див. Мій допис у блозі blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

Відповіді:


92

Якщо ви використовуєте ASP.NET MVC 2 Preview 2 або вище , тепер ви можете просто використовувати:

[RequireHttps]
public ActionResult Login()
{
   return View();
}

Хоча параметр замовлення вартий уваги, як уже згадувалося тут .


23
Ви також можете зробити це на рівні контролера. А ще краще, якщо ви хочете, щоб уся програма була SSL, ви можете створити базовий контролер, розширити його для всіх контролерів і застосувати там атрибут.
ashes999

22
Крім того, ви можете додати, що це глобальний фільтр MVC3 у Global.asax GlobalFilters.Filters.Add (new RequireHttpsAttribute ());
GraemeMiller

2
Жодні гарантовано інші розробники не будуть використовувати ваш похідний контролер. Ви можете зробити один дзвінок, щоб примусити HTTPS - див. Мій допис у блозі blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

17

MVCFutures має атрибут 'RequireSSL'.

(дякую Адаму, що вказав на це у своєму оновленому дописі блогу)

Просто застосуйте його до свого методу дії, використовуючи 'Redirect = true', якщо ви хочете, щоб запит http: // автоматично перетворився на https: //:

    [RequireSsl(Redirect = true)]

Див. Також: ASP.NET MVC RequireHttps лише у виробництві


Чи потрібно було б підкласувати його для обробки запитів localhost?
Містер Роджерс

один із способів - створити сертифікат для вашої локальної машини та використовувати його. Я думаю, щоб повністю вимкнути його для localhost, вам дійсно потрібно буде підкласувати або продублювати код. не впевнений, який рекомендований підхід
Simon_Weaver

1
Схоже, він запечатаний, тому мені потрібно буде обдурити код. Облом. Сертифікат локальної машини працюватиме лише в IIS, хоча і правильно, а не на веб-сервері розробника.
Містер Роджерс

@mr Rogers - подивіться на це: stackoverflow.com/questions/1639707 / ...
Simon_Weaver

Оновлення до MVC4 + див. Мій запис у блозі blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

9

Як писав Амадьєр , [RequireHttps] чудово працює в MVC 2 для введення HTTPS. Але якщо ви хочете використовувати HTTPS лише для деяких сторінок, як ви вже сказали, MVC 2 не дарує вам жодної любові - як тільки він перемикає користувача на HTTPS, вони затримуються там, доки ви не перенаправите їх вручну.

Я використав підхід до використання іншого користувацького атрибута, [ExitHttpsIfNotRequired]. При підключенні до контролера або дії це перенаправляє на HTTP, якщо:

  1. Запит був HTTPS
  2. Атрибут [RequireHttps] не застосовувався до дії (або контролера)
  3. Запит був GET (перенаправлення POST призвело б до різноманітних проблем).

Він занадто великий, щоб розміщувати його тут, але ви можете побачити код тут, а також деякі додаткові подробиці.


AllowAnonymous це виправляє. Щодо MVC4 та новіших версій, див. Мій допис у блозі blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

8

Ось нещодавня публікація від Дена Уоліна про це:

http://weblogs.asp.net/dwahlin/archive/2009/08/25/reaching-ssl-for-asp-net-mvc-controllers.aspx

Він використовує атрибут ActionFilter.


2
На даний момент це найкращий спосіб.
royco

+1 через рік, оскільки дзвінок isLocal допоміг мені вирішити проблему, яка ставала справжнім болем в @@@
heisenberg

1
Вище зазначене, для MVC4 і новіших версій, див. Мій допис у блозі blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT


3

Для тих, хто не є шанувальником атрибутивно-орієнтованих підходів до розробки, ось фрагмент коду, який може допомогти:

public static readonly string[] SecurePages = new[] { "login", "join" };
protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
    var pageName = RequestHelper.GetPageNameOrDefault();
    if (!HttpContext.Current.Request.IsSecureConnection
        && (HttpContext.Current.Request.IsAuthenticated || SecurePages.Contains(pageName)))
    {
        Response.Redirect("https://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl);
    }
    if (HttpContext.Current.Request.IsSecureConnection
        && !HttpContext.Current.Request.IsAuthenticated
        && !SecurePages.Contains(pageName))
    {
        Response.Redirect("http://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl);
    }
}

Існує декілька причин уникати атрибутів, і одна з них - якщо ви хочете переглянути список усіх захищених сторінок, вам доведеться перейти через усі контролери в рішенні.


Я думаю, що більшість людей не погодиться з вами щодо цього, хоча надання альтернативного способу завжди корисно ...
Serj Sagan

2

Я зіткнувся з цим питанням і сподіваюся, що моє рішення може комусь допомогти.

У нас було декілька проблем: - Нам потрібно захистити певні дії, наприклад "LogOn" в "Account". Ми можемо використовувати збірку в атрибуті RequireHttps, що чудово - але це перенаправить нас назад за допомогою https: //. - Ми повинні робити наші посилання, форми та такі "SSL обізнані".

Як правило, моє рішення дозволяє вказати маршрути, які використовуватимуть абсолютну URL-адресу, крім можливості вказати протокол. Ви можете використовувати цей підхід для вказівки протоколу "https".

Отже, спочатку я створив перелік ConnectionProtocol:

/// <summary>
/// Enum representing the available secure connection requirements
/// </summary>
public enum ConnectionProtocol
{
    /// <summary>
    /// No secure connection requirement
    /// </summary>
    Ignore,

    /// <summary>
    /// No secure connection should be used, use standard http request.
    /// </summary>
    Http,

    /// <summary>
    /// The connection should be secured using SSL (https protocol).
    /// </summary>
    Https
}

Тепер я створив ручну версію RequireSsl. Я змінив вихідний вихідний код RequireSsl, щоб дозволити перенаправлення назад на http: // urls. Крім того, я поставив поле, яке дозволяє нам визначити, чи потрібно нам вимагати SSL чи ні (я використовую його з попереднім процесором DEBUG).

/* Note:
 * This is hand-rolled version of the original System.Web.Mvc.RequireHttpsAttribute.
 * This version contains three improvements:
 * - Allows to redirect back into http:// addresses, based on the <see cref="SecureConnectionRequirement" /> Requirement property.
 * - Allows to turn the protocol scheme redirection off based on given condition.
 * - Using Request.IsCurrentConnectionSecured() extension method, which contains fix for load-balanced servers.
 */
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
    public RequireHttpsAttribute()
    {
        Protocol = ConnectionProtocol.Ignore;
    }

    /// <summary>
    /// Gets or sets the secure connection required protocol scheme level
    /// </summary>
    public ConnectionProtocol Protocol { get; set; }

    /// <summary>
    /// Gets the value that indicates if secure connections are been allowed
    /// </summary>
    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");
        }

        /* Are we allowed to use secure connections? */
        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)
    {
        // only redirect for GET requests, otherwise the browser might not propagate the verb and request
        // body correctly.

        if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
        }

        // redirect to HTTPS version of page
        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.");
        }

        // redirect to HTTP version of page
        string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
        filterContext.Result = new RedirectResult(url);
    }
}

Тепер цей RequireSsl зробить наступну базу на вашому значенні атрибуту Requirements: - Ignore: Нічого не робитиме. - Http: Примусове перенаправлення на протокол http. - Https: Примусове перенаправлення на протокол https.

Вам слід створити власний базовий контролер і встановити цей атрибут Http.

[RequireSsl(Requirement = ConnectionProtocol.Http)]
public class MyController : Controller
{
    public MyController() { }
}

Тепер у кожному cpntroller / дії, для якого ви хочете вимагати SSL - просто встановіть цей атрибут за допомогою ConnectionProtocol.Https.

Тепер давайте перейдемо до URL-адрес: У нас було кілька проблем з механізмом маршрутизації URL-адрес. Ви можете прочитати більше про них на веб-сайті http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . Рішення, запропоноване в цій публікації, теоретично хороше, але старе, і я не люблю схвалення.

Моє рішення полягає в наступному: Створіть підклас базового класу "Маршрут":

відкритий клас AbsoluteUrlRoute: Маршрут {#region ctor

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    public AbsoluteUrlRoute(string url, IRouteHandler routeHandler)
        : base(url, routeHandler)
    {

    }

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
        : base(url, defaults, routeHandler)
    {

    }

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param>
    /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,
                            IRouteHandler routeHandler)
        : base(url, defaults, constraints, routeHandler)
    {

    }

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param>
    /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
    /// <param name="dataTokens">Custom values that are passed to the route handler, but which are not used
    ///     to determine whether the route matches a specific URL pattern. These values
    ///     are passed to the route handler, where they can be used for processing the
    ///     request.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    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

    /// <summary>
    /// Creates an absolute url
    /// </summary>
    /// <param name="requestContext">The request context</param>
    /// <param name="virtualPath">The initial virtual relative path</param>
    /// <param name="scheme">The protocol scheme</param>
    /// <returns>The absolute URL</returns>
    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
}

Ця версія класу "Маршрут" створить абсолютну URL-адресу. Фокус, за яким слідує пропозиція автора допису в блозі, полягає у використанні DataToken для визначення схеми (приклад в кінці :)).

Тепер, якщо ми будемо генерувати URL-адресу, наприклад, для маршруту "Account / LogOn", ми отримаємо "/ http://example.com/Account/LogOn " - це тому, що UrlRoutingModule розглядає всі URL-адреси як відносні. Ми можемо це виправити за допомогою користувацького HttpModule:

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

Оскільки цей модуль замінює базову реалізацію UrlRoutingModule, нам слід видалити базовий httpModule та зареєструвати наш у web.config. Отже, під "system.web" встановіть:

<httpModules>
  <!-- Removing the default UrlRoutingModule and inserting our own absolute url routing module -->
  <remove name="UrlRoutingModule-4.0" />
  <add name="UrlRoutingModule-4.0" type="MyApp.Web.Mvc.Routing.AbsoluteUrlRoutingModule" />
</httpModules>

Це воно :).

Для того, щоб зареєструвати абсолютний / протокол, яким слід маршрут, вам слід зробити:

        routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}),
                DataTokens = new RouteValueDictionary(new {scheme = "https"})
            });

Будемо раді почути ваші відгуки + вдосконалення. Сподіваюся, це може допомогти! :)

Редагувати: я забув включити метод розширення IsCurrentConnectionSecured () (занадто багато фрагментів: P). Це метод розширення, який зазвичай використовує Request.IsSecuredConnection. Однак цей підхід не буде працювати при використанні балансування навантаження - тому цей метод може обійти це (взяте з nopCommerce).

    /// <summary>
    /// Gets a value indicating whether current connection is secured
    /// </summary>
    /// <param name="request">The base request context</param>
    /// <returns>true - secured, false - not secured</returns>
    /// <remarks><![CDATA[ This method checks whether or not the connection is secured.
    /// There's a standard Request.IsSecureConnection attribute, but it won't be loaded correctly in case of load-balancer.
    /// See: <a href="http://nopcommerce.codeplex.com/SourceControl/changeset/view/16de4a113aa9#src/Libraries/Nop.Core/WebHelper.cs">nopCommerce WebHelper IsCurrentConnectionSecured()</a>]]></remarks>
    public static bool IsCurrentConnectionSecured(this HttpRequestBase request)
    {
        return request != null && request.IsSecureConnection;

        //  when your hosting uses a load balancer on their server then the Request.IsSecureConnection is never got set to true, use the statement below
        //  just uncomment it
        //return request != null && request.ServerVariables["HTTP_CLUSTER_HTTPS"] == "on";
    }




0

MVC 6 (ASP.NET Core 1.0) працює трохи інакше з Startup.cs.

Щоб використовувати RequireHttpsAttribute (як згадано у відповіді Amadiere) на всіх сторінках, ви можете додати це в Startup.cs, замість того, щоб використовувати стиль атрибута на кожному контролері (або замість того, щоб створювати BaseController для всіх ваших контролерів, які потрібно успадкувати).

Startup.cs - фільтр реєстру:

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

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

Для отримання додаткової інформації про дизайнерські рішення для вищезазначеного підходу дивіться мою відповідь на подібне запитання про те, як виключити запити localhost із обробки за допомогою RequireHttpsAttribute .


0

По черзі додайте фільтр до Global.asax.cs

GlobalFilters.Filters.Add (новий RequireHttpsAttribute ());

Клас RequireHttpsAttribute

using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace xxxxxxxx
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            GlobalFilters.Filters.Add(new RequireHttpsAttribute());
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.