httpまたはhttpsでのアクセスを強制するカスタムフィルター

httpsでしかアクセスさせたくないページの場合、そのページのアクションメソッドに [RequireHttps]属性を付けると、httpでアクセスしたとき自動時にhttpsにリダイレクトさせることができます。

でもその逆、httpsでアクセスしたときにhttpにリダイレクトさせるような属性は用意されていません。なので自分で作る必要があります。

これ既に作っていた人がいて、それがこちらの記事。消えちゃってるのでInternetArchiveとGistの直リンク。

このカスタムフィルター属性結構良いと思うのですが、2点おしいところがあります。

  1. SSLSSLページで異なるドメイン*1やポートを使用している場合対応できない*2
  2. クエリストリングやアンカー(#)を引き継いでリダイレクトできない*3

1は開発サイトとかでありがち。

その辺を考慮して少し修正した対応版作りました。

using System.Web.Mvc;
 
namespace Utils.Filter
{
    public enum Scheme
    {
        Ignore,
        Http,
        Https,
    }
 
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class RequireHttpSchemeAttribute
        : FilterAttribute, IAuthorizationFilter
    {
        private readonly Scheme scheme;
 
        public RequireHttpSchemeAttribute(Scheme scheme)
        {
            this.scheme = scheme;
        }
 
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if ((scheme == Scheme.Https) && !filterContext.HttpContext.Request.IsSecureConnection)
            {
                filterContext.Result = GetResult(filterContext, "https://localhost:1443");
            }
            else if ((scheme == Scheme.Http) && filterContext.HttpContext.Request.IsSecureConnection)
            {
                filterContext.Result = GetResult(filterContext, "http://localhost:1180");
            }
        }
 
        private static RedirectResult GetResult(AuthorizationContext filterContext, string baseUrl)
        {
            var uri = new Uri(baseUrl);
 
            return new RedirectResult(uri.GetLeftPart(UriPartial.Authority) + filterContext.HttpContext.Request.RawUrl);
        }
    }
}

URLベタ書きの部分は普通はWeb.configとかに記述してあると思うからそれを使いましょう。

使い方

こんな感じです。

[RequireHttpScheme(Scheme.Http)]
public virtual ActionResult HttpOnly()
{
    return View();
}

これはhttpsでアクセスされた場合、httpにリダイレクトする。

[RequireHttpScheme(Scheme.Https)]
public virtual ActionResult HttpsOnly()
{
    return View();
}

これはhttpでアクセスされた場合、httpsにリダイレクトする。

[RequireHttpScheme(Scheme.Https)]
public class AlmostSecureController : Controller
{
    public virtual ActionResult HttpsOnly()
    {
        return View();
    }

    [RequireHttpScheme(Scheme.Ignore)]
    public virtual ActionResult DoesntMatter()
    {
        return View();
    }
}

これはコントローラー自体に属性が付いていますので、このコントローラー内の全てのアクションメソッドでhttpsでのアクセスを強制しようとしています。しかし例外として特定のアクションメソッドにはリダイレクト処理をさせたくないという場合があります。
そういうときに例外にしたいアクションメソッドにScheme.Ignoreを設定したRequireHttpScheme属性を付けてあげると、そのアクションメソッドでは何も起きなくなります。*4 一つのコントローラー内に沢山アクションメソッドがある場合に便利かもしれませんね。

*1:IPも

*2:これはRequireHttps属性も対応してない

*3:UrlBuilder.Pathにクエリストリング以下を設定するとランタイムエラーになる

*4:でもこういう場合は往々にしてこのメソッドだけhttpアクセスを強制させたかったりするので、Scheme.IgnoreじゃなくてScheme.httpを設定することになると思う