如何在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




