在ASP.NET MVC專案新增了開發偵錯專用的Controller,某些Action想限定從localhost存取,以免遭到誤用。逐一在Action加入檢查IP邏輯是種做法,但如此有點浪費ASP.NET MVC強大的擴充性,就好比提著子彈上膛的M16步槍上戰場,不扣板機卻拿槍托狂敲敵人的頭,不免有暴殄天物之憾。

ASP.NET MVC有個Filter機制(中文翻成篩選器),在Filter可自訂執行Action時要一併觸發的邏輯,為Action加上[FilterName] Attribute,便可在該Action之前或之後插入自訂邏輯。這種概念很適合用來實現Log記錄、權限控管、Exception處理... 等等通用性任務。而IAuthorizationFilter介面專司權限管控,符合IP篩選的安全性質,因此我的構想是寫個類別,實做IAuthorizationFilter介面的OnAuthorization()方法,由傳入的AuthorizationContext取得UserHostAddress判斷來源IP,若HttpRequest不是來自本機(localhost)即拋出錯誤,就實現了Action只開放本機存取的效果。

FilterAttribute完成後,任何Action只要加上該FilterAttribute宣告,就會自動套用上述檢查限定本機存取,非常簡便易用。

以下是完整程式範例,為了增加應用彈性,我特別再抽出一層AllowedIpOnlyAttribute,宣告時傳入允許存取的IP清單;而LocalhostOnlyAttribute繼承AllowedIpOnlyAttribute,將允許IP寫死::1(IPv6)及127.0.0.1,成為AllowedIpOnlyAttribute的特例,做到限定本機存取。如此一魚兩吃,一次獲得兩種Filter,適用於不同情境。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace MyWeb.Models
{
    public class AllowedIpOnlyAttribute : FilterAttribute, IAuthorizationFilter
    {
        private string[] ipList = new string[] {};
        //建構式接收以逗號或分號分隔的IP清單,限定存取來源
        //TODO: 如要方便事後修改,可擴充成由config讀取IP清單,但會增加被破解風險
        public AllowedIpOnlyAttribute(string allowedIps)
        {
            ipList = allowedIps.Split(',', ';');
        }
        #region IAuthorizationFilter Members
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            //實作OnAuthorization,當來源IP不在清單上,彈出錯誤
            string clientIp = filterContext.HttpContext.Request.UserHostAddress;
            if (!ipList.Contains(clientIp))
                throw new ApplicationException("Disallowed Client IP!");
        }
        #endregion
    }
    //限定本機存取為AllowedIpOnlyAttribute的特殊情境,限定IP=::1或127.0.0.1
    public class LocalhostOnlyAttribute : AllowedIpOnlyAttribute
    {
        public LocalhostOnlyAttribute()
            : base("::1;127.0.0.1")
        {
        }
    }
}

接著,見識Filter便利性的時刻來了,在Action加上AllowedIpOnly或LocalhostOnly,Action立刻變成限定特定IP或本機IP才能使用! 很方便吧?

        [AllowedIpOnly("192.168.1.100")]
        public ActionResult IpOnly()
        {
            return Content("IpOnly");
        }
        [LocalhostOnly]
        public ActionResult LocalhostOnly()
        {
            return Content("LocalHostOnly");
        }

Comments

Be the first to post a comment

Post a comment