You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在MVC .Net中基于角色限制用户外网访问系统?

嗨,这个需求在ASP.NET MVC开发里挺常见的,我来给你梳理下具体的实现思路和步骤,结合角色验证和网络来源判断就能搞定,分配置和代码逻辑两部分来做就很清晰了:

核心思路

我们需要做双重验证:一方面通过用户角色判断是否允许外部访问,另一方面验证当前请求的客户端IP是否属于公司内网范围。默认的[Authorize]特性只处理角色,所以我们需要扩展它,把网络验证的逻辑加进去。

具体实现步骤

第一步:配置内网IP范围

先把公司内网的IP段配置在配置文件里,方便后续修改,不用硬编码代码。

.NET Core/.NET 5+(appsettings.json)

"AllowedInternalNetworks": [
  "192.168.0.0/24",
  "10.0.0.0/8"
]

.NET Framework(web.config)

<appSettings>
  <add key="AllowedInternalNetworks" value="192.168.0.0/24,10.0.0.0/8" />
</appSettings>

第二步:实现网络验证工具类

写一个工具类来判断客户端IP是否属于内网,还要注意处理代理服务器的情况(比如Nginx、IIS ARR),确保拿到真实的客户端IP。

public static class NetworkHelper
{
    // 判断IP是否在允许的内网段内
    public static bool IsInternalNetwork(string clientIp, List<string> allowedNetworks)
    {
        if (string.IsNullOrEmpty(clientIp)) return false;
        var ipAddress = IPAddress.Parse(clientIp);
        
        foreach (var network in allowedNetworks)
        {
            var parts = network.Split('/');
            var networkAddress = IPAddress.Parse(parts[0]);
            var prefixLength = int.Parse(parts[1]);
            var subnetMask = GetSubnetMask(prefixLength);
            
            var networkBytes = networkAddress.GetAddressBytes();
            var ipBytes = ipAddress.GetAddressBytes();
            var isMatch = true;
            
            for (int i = 0; i < subnetMask.Length; i++)
            {
                if ((ipBytes[i] & subnetMask[i]) != networkBytes[i])
                {
                    isMatch = false;
                    break;
                }
            }
            if (isMatch) return true;
        }
        return false;
    }

    // 生成子网掩码
    private static byte[] GetSubnetMask(int prefixLength)
    {
        var mask = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            if (prefixLength >= 8)
            {
                mask[i] = 0xFF;
                prefixLength -= 8;
            }
            else
            {
                mask[i] = (byte)(0xFF << (8 - prefixLength));
                prefixLength = 0;
            }
        }
        return mask;
    }

    // .NET Framework 获取真实客户端IP(处理代理)
    public static string GetClientIp(HttpRequestBase request)
    {
        var ip = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
        if (!string.IsNullOrEmpty(ip))
        {
            // 多代理场景下取第一个非本地IP
            var ipList = ip.Split(',');
            foreach (var item in ipList)
            {
                var realIp = item.Trim();
                if (!realIp.StartsWith("127.") && !realIp.StartsWith("192.168.") && !realIp.Equals("::1"))
                {
                    return realIp;
                }
            }
        }
        return request.ServerVariables["REMOTE_ADDR"];
    }

    // .NET Core 获取真实客户端IP(处理代理)
    public static string GetClientIp(HttpRequest request)
    {
        var ip = request.Headers["X-Forwarded-For"].FirstOrDefault();
        if (!string.IsNullOrEmpty(ip))
        {
            var ipList = ip.Split(',');
            foreach (var item in ipList)
            {
                var realIp = item.Trim();
                if (!realIp.StartsWith("127.") && !realIp.StartsWith("192.168.") && !realIp.Equals("::1"))
                {
                    return realIp;
                }
            }
        }
        return request.HttpContext.Connection.RemoteIpAddress?.ToString();
    }
}

第三步:自定义Authorize特性

扩展默认的[Authorize],加入角色和网络的双重验证逻辑:

.NET Framework MVC版本

public class AuthorizeWithNetworkAttribute : AuthorizeAttribute
{
    // 允许外部访问的角色,多个用逗号分隔
    public string AllowExternalRoles { get; set; }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        // 先验证基础身份和角色
        if (!base.AuthorizeCore(httpContext))
        {
            return false;
        }

        var user = httpContext.User;
        var allowedExternalRoles = AllowExternalRoles.Split(',').Select(r => r.Trim()).ToList();
        var isInExternalRole = allowedExternalRoles.Any(role => user.IsInRole(role));

        // 如果是允许外部访问的角色,直接通过
        if (isInExternalRole)
        {
            return true;
        }

        // 否则检查是否来自内网
        var clientIp = NetworkHelper.GetClientIp(httpContext.Request);
        var allowedNetworks = ConfigurationManager.AppSettings["AllowedInternalNetworks"]
            .Split(',').Select(n => n.Trim()).ToList();
        return NetworkHelper.IsInternalNetwork(clientIp, allowedNetworks);
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // 自定义未授权提示
        filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden, "你无权从当前网络访问此应用");
    }
}

.NET Core/.NET MVC版本

public class AuthorizeWithNetworkAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public string AllowExternalRoles { get; set; }
    private readonly IConfiguration _configuration;

    public AuthorizeWithNetworkAttribute(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;
        if (!user.Identity.IsAuthenticated)
        {
            context.Result = new ChallengeResult();
            return;
        }

        var allowedExternalRoles = AllowExternalRoles.Split(',').Select(r => r.Trim()).ToList();
        var isInExternalRole = allowedExternalRoles.Any(role => user.IsInRole(role));

        if (isInExternalRole)
        {
            return;
        }

        var clientIp = NetworkHelper.GetClientIp(context.HttpContext.Request);
        var allowedNetworks = _configuration.GetSection("AllowedInternalNetworks").Get<List<string>>();
        var isInternal = NetworkHelper.IsInternalNetwork(clientIp, allowedNetworks);

        if (!isInternal)
        {
            context.Result = new ForbidResult("你无权从当前网络访问此应用");
        }
    }
}

第四步:在控制器/Action上使用

假设ExternalUser角色允许外部访问,InternalOnlyUser只能内网访问,用法如下:

// 整个控制器应用验证规则
[AuthorizeWithNetwork(AllowExternalRoles = "ExternalUser")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    // 单独给某个Action设置允许外部访问的角色
    [AuthorizeWithNetwork(AllowExternalRoles = "Admin,ExternalUser")]
    public ActionResult ExternalAllowedAction()
    {
        return View();
    }
}
额外注意事项
  • 代理服务器配置:如果应用部署在反向代理后面,一定要确保代理传递真实客户端IP(比如设置X-Forwarded-For头),否则会拿到代理服务器的IP导致验证错误。
  • IP段准确性:确认公司内网的IP段(包括IPv4/IPv6),如果需要支持IPv6,要修改NetworkHelper的IP处理逻辑。
  • 测试验证:分别在内网和外部网络测试不同角色的用户,确保逻辑符合预期。
  • 权限细化:如果需要某些Action强制内网访问,可扩展[InternalOnly]特性,或者在自定义特性中增加参数配置。

内容的提问来源于stack exchange,提问作者Pooja Sharma

火山引擎 最新活动