分享 ASP.NET MVC 私房小技巧一則。
AJAX 呼叫 ASP.NET MVC 時,基於安全考量應限定 POST 方法。(參考:隱含殺機的GET式AJAX資料更新 - 黑暗執行緒)
不過在開放測試階段,開放 GET 可在瀏覽器網址列輸入 URL 測試較方便,有沒有兩全其美的方法?
於是我寫了一個 Action Attribute,實現「從 localhost 呼叫可用 GET,從正常 IP 存取只能 POST」的效果,像是這樣:
HomeController 的 Test Action(),瀏覽器透過 localhost 可讀取,改用實際 IP 則傳回 HTTP 404。應用方法很簡單,在 Action 加上 [LocalHttpGetOrHttpPost] 即可,基於安全考量,預設只開放 POST,必須在 appSetting 加入 <add key="EnableRestrictedGet" value="true"/> 才開放本機使用 GET :
[LocalHttpGetOrHttpPost]
public ActionResult Test()
{
return Content("Test OK");
}
為求彈性起見,再多支援透過 appSetting 指定開放 GET 存取的 IP 清單。例如:
[RestrictedHttpGetOrHttpPost("TestIps")]
public ActionResult Test()
{
return Content("Test OK");
}
設定檔需加入對應的 IP 清單:
<appSettings> <add key="EnableRestrictedGet" value="true"/> <add key="AllowGetIPs" value="::1,127.0.0.1,172.28.1.1"/> </appSettings>
完整程式範例如下,有需要的同學請自取:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
namespace System.Web.Mvc
{
public class LocalHttpGetOrHttpPostAttribute : RestrictedHttpGetOrHttpPostAttribute
{
public LocalHttpGetOrHttpPostAttribute() : base("localhost")
{
}
}
public class RestrictedHttpGetOrHttpPostAttribute : ActionMethodSelectorAttribute
{
//REF: https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Mvc/HttpGetAttribute.cs
private static readonly AcceptVerbsAttribute getCheck = new AcceptVerbsAttribute(HttpVerbs.Get);
private static readonly AcceptVerbsAttribute postCheck = new AcceptVerbsAttribute(HttpVerbs.Post);
private static readonly bool EnableRestrictedGet =
System.Configuration.ConfigurationManager.AppSettings["EnableRestrictedGet"] == "true";
private readonly string[] AllowedGetIPAddresses = "::1,127.0.0.1".Split(',');
public RestrictedHttpGetOrHttpPostAttribute(string allowedIpSettingName)
{
if (allowedIpSettingName != "localhost")
{
var allowedIps = System.Configuration.ConfigurationManager.AppSettings[allowedIpSettingName];
if (string.IsNullOrEmpty(allowedIps))
throw new ArgumentException($"appSetting '{allowedIpSettingName}' not found!");
AllowedGetIPAddresses = allowedIps.Split(',', ';');
}
}
public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo)
{
return
postCheck.IsValidForRequest(controllerContext, methodInfo) ||
EnableRestrictedGet &&
getCheck.IsValidForRequest(controllerContext, methodInfo) &&
AllowedGetIPAddresses.Contains(controllerContext.HttpContext.Request.UserHostAddress);
}
}
}