如何限制JavaScript文件仅授权站点可访问?ASP.NET MVC/Core方案
嘿,这个场景我之前帮朋友处理过,刚好能给你一些实用的方案。核心思路就是只让你授权的站点通过合法请求拿到这个JS文件,咱们分ASP.NET MVC和ASP.NET Core两种环境来具体说:
在ASP.NET MVC中的实现方式
方案1:检查Referer请求头(快速入门)
这个方案适合对安全性要求不是极高的场景,利用请求的Referer头判断来源是否合法——这个头会告诉服务器请求是从哪个站点发起的。
你可以自定义一个Action过滤器,拦截返回JS文件的请求:
public class AuthorizeReferrerAttribute : ActionFilterAttribute { // 这里放你的授权站点名单 private readonly List<string> _allowedHosts = new List<string> { "your-allowed-site1.com", "your-allowed-site2.com" }; public override void OnActionExecuting(ActionExecutingContext filterContext) { var referrer = filterContext.HttpContext.Request.UrlReferrer; // 没有Referer或者不在授权列表里,直接返回403 if (referrer == null || !_allowedHosts.Contains(referrer.Host, StringComparer.OrdinalIgnoreCase)) { filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden); return; } base.OnActionExecuting(filterContext); } }
然后把你的JS文件从静态目录移出来,通过控制器Action返回,给这个Action加上过滤器:
public class JsController : Controller { [AuthorizeReferrer] public ActionResult ProtectedScript() { // 读取JS文件内容,路径根据你的实际存储位置调整 var jsContent = System.IO.File.ReadAllText(Server.MapPath("~/PrivateScripts/MyProtected.js")); return Content(jsContent, "application/javascript"); } }
授权站点就可以通过https://your-server.com/Js/ProtectedScript来引用这个JS了。
方案2:签名验证(防伪造,更安全)
Referer头是可以被伪造的,如果你需要更高的安全性,就用签名验证:让授权站点请求时带上用密钥生成的签名,服务器端验证签名合法性。
示例控制器代码:
public class JsController : Controller { // 存储授权站点的ID和对应密钥 private readonly Dictionary<string, string> _allowedSites = new Dictionary<string, string> { {"site1", "your-strong-secret-key-1"}, {"site2", "your-strong-secret-key-2"} }; public ActionResult ProtectedScript(string siteId, string timestamp, string signature) { // 先验证站点是否在授权列表里 if (!_allowedSites.TryGetValue(siteId, out var secretKey)) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } // 验证时间戳,防止重放攻击(这里限制5分钟内的请求有效) if (!long.TryParse(timestamp, out var requestTime) || DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds - requestTime > 300) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } // 生成预期签名并对比 var expectedSignature = GenerateSignature($"{siteId}{timestamp}", secretKey); if (!string.Equals(signature, expectedSignature, StringComparison.OrdinalIgnoreCase)) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } // 返回JS内容 var jsContent = System.IO.File.ReadAllText(Server.MapPath("~/PrivateScripts/MyProtected.js")); return Content(jsContent, "application/javascript"); } // 生成HMAC签名的方法 private string GenerateSignature(string data, string secretKey) { using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey))) { var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data)); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } } }
授权站点需要用自己的siteId和密钥生成签名,拼在请求URL里,比如:https://your-server.com/Js/ProtectedScript?siteId=site1×tamp=1700000000&signature=abcdef123456...
在ASP.NET Core中的实现方式
方案1:Referer验证中间件
Core里用中间件拦截静态文件请求更灵活,先写一个中间件:
public class ReferrerValidationMiddleware { private readonly RequestDelegate _next; private readonly List<string> _allowedHosts = new List<string> { "your-allowed-site1.com", "your-allowed-site2.com" }; // 要保护的JS文件路径 private readonly string _protectedJsPath = "/js/my-protected.js"; public ReferrerValidationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 只拦截目标JS文件的请求 if (context.Request.Path.Equals(_protectedJsPath, StringComparison.OrdinalIgnoreCase)) { if (!context.Request.Headers.TryGetValue("Referer", out var referrerValue)) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsync("Forbidden: No referrer provided"); return; } if (!Uri.TryCreate(referrerValue, UriKind.Absolute, out var referrerUri) || !_allowedHosts.Contains(referrerUri.Host, StringComparer.OrdinalIgnoreCase)) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsync("Forbidden: Unauthorized referrer"); return; } } // 验证通过,继续处理请求 await _next(context); } } // 扩展方法,方便注册中间件 public static class ReferrerValidationMiddlewareExtensions { public static IApplicationBuilder UseReferrerValidation(this IApplicationBuilder builder) { return builder.UseMiddleware<ReferrerValidationMiddleware>(); } }
然后在Program.cs里注册这个中间件,一定要放在UseStaticFiles之前:
var app = builder.Build(); // 先验证Referrer,再处理静态文件 app.UseReferrerValidation(); app.UseStaticFiles(); // ...其他中间件
方案2:签名验证控制器
和MVC的思路一致,Core里可以结合配置文件存储密钥,示例代码:
[ApiController] [Route("api/js")] public class JsController : ControllerBase { private readonly IConfiguration _config; public JsController(IConfiguration config) { _config = config; } [HttpGet("protected")] public IActionResult GetProtectedJs(string siteId, string timestamp, string signature) { // 从配置文件读取对应站点的密钥 var secretKey = _config.GetValue<string>($"AllowedSites:{siteId}:SecretKey"); if (string.IsNullOrEmpty(secretKey)) { return Forbid(); } // 验证时间戳 if (!long.TryParse(timestamp, out var requestTime) || DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds - requestTime > 300) { return Forbid(); } // 验证签名 var expectedSignature = GenerateSignature($"{siteId}{timestamp}", secretKey); if (!string.Equals(signature, expectedSignature, StringComparison.OrdinalIgnoreCase)) { return Forbid(); } // 读取JS文件内容 var jsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "js", "my-protected.js"); var jsContent = System.IO.File.ReadAllText(jsPath); return Content(jsContent, "application/javascript"); } private string GenerateSignature(string data, string secretKey) { using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey))) { var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data)); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } } }
配置文件appsettings.json里添加授权站点信息:
"AllowedSites": { "site1": { "SecretKey": "your-strong-secret-key-1" }, "site2": { "SecretKey": "your-strong-secret-key-2" } }
关键注意事项
- Referer头的局限性:部分浏览器或代理会屏蔽Referer头,且这个头可以被伪造,所以只适合作为基础防护。
- 签名方案的安全性:一定要保管好密钥,绝对不能泄露给第三方;时间戳验证能有效防止重放攻击。
- 静态文件处理:受保护的JS不能直接放在静态目录(比如wwwroot),必须通过控制器或中间件拦截返回,否则验证逻辑会失效。
- 强制HTTPS:所有请求都要使用HTTPS,防止请求参数被窃听或篡改。
内容的提问来源于stack exchange,提问作者Ian Vink
相关产品推荐
相关产品推荐

