From 5c871ccb8796cceb67790515a4590c353d7507ad Mon Sep 17 00:00:00 2001 From: wayne Date: Thu, 29 Jul 2021 21:02:39 +0800 Subject: [PATCH] Cross-Site Request Forgery (CSRF) --- .../Controllers/BasketController.cs | 22 ++++++++-------- src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.js | 10 +++++--- .../Scripts/ZKEACMS.Basket.min.js | 2 +- src/ZKEACMS.WebHost/DefaultResourceManager.cs | 4 +++ .../Controllers/AntiforgeryController.cs | 25 +++++++++++++++++++ src/ZKEACMS/Route/RouteDescriptors.cs | 7 ++++++ 6 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 src/ZKEACMS/Controllers/AntiforgeryController.cs diff --git a/src/ZKEACMS.Shop/Controllers/BasketController.cs b/src/ZKEACMS.Shop/Controllers/BasketController.cs index f9bd4551..01168976 100644 --- a/src/ZKEACMS.Shop/Controllers/BasketController.cs +++ b/src/ZKEACMS.Shop/Controllers/BasketController.cs @@ -16,29 +16,31 @@ namespace ZKEACMS.Shop.Controllers [CustomLoginCheck] public class BasketController : Controller { - private CMSApplicationContext applicationContext; private readonly IBasketService _basketService; - public BasketController(IBasketService basketService, IApplicationContextAccessor applicationContextAccessor) + public BasketController(IBasketService basketService) { _basketService = basketService; - applicationContext = applicationContextAccessor.Current; } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult Index() { return View(new BasketData(_basketService.Get())); } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult Add(int productId, int? quantity, string tags) { + if (quantity.HasValue && quantity.Value < 1) return BadRequest(); + var basket = new Basket { ProductId = productId, Quantity = quantity ?? 1, Description = tags }; _basketService.Add(basket); return Json(new AjaxResult { Status = AjaxStatus.Normal, Data = new BasketData(_basketService.Get()) }); } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult Update(int basketId, int quantity) { + if (quantity < 1) return BadRequest(); + var basket = _basketService.Get(basketId); if (basket != null) { @@ -47,18 +49,18 @@ public IActionResult Update(int basketId, int quantity) } return Json(new AjaxResult { Status = AjaxStatus.Normal, Data = new BasketData(_basketService.Get()) }); } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult Remove(int basketId) { _basketService.Remove(basketId); return Json(new AjaxResult { Status = AjaxStatus.Normal, Data = new BasketData(_basketService.Get()) }); } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult GetBaskets() { return Json(new AjaxResult { Status = AjaxStatus.Normal, Data = new BasketData(_basketService.Get()) }); } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult CheckOut() { var basket = _basketService.Get(); @@ -68,7 +70,7 @@ public IActionResult CheckOut() } return View("Index", new BasketData(basket)); } - [HttpPost] + [HttpPost, ValidateAntiForgeryToken] public IActionResult ConfirmOrder(Order order) { order = _basketService.CheckOut(order); diff --git a/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.js b/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.js index 5b4c2ea1..dff5fb84 100644 --- a/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.js +++ b/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.js @@ -5,6 +5,7 @@ var ZKEACMS = ZKEACMS || {}; ZKEACMS.Basket = { Add: function (product, callBack) { + Object.assign(product, ZKEACMS.AntiToken); $.post("/Basket/Add", product, function (data) { if (data.location) { window.location = data.location; @@ -16,6 +17,7 @@ ZKEACMS.Basket = { }); }, Update: function (basket, callBack) { + Object.assign(basket, ZKEACMS.AntiToken); $.post("/Basket/Update", basket, function (data) { if (data.location) { window.location = data.location; @@ -27,7 +29,7 @@ ZKEACMS.Basket = { }); }, Remove: function (basketId, callBack) { - $.post("/Basket/Remove", { basketId: basketId }, function (data) { + $.post("/Basket/Remove", Object.assign({ basketId: basketId }, ZKEACMS.AntiToken), function (data) { if (data.location) { window.location = data.location; } else { @@ -38,14 +40,14 @@ ZKEACMS.Basket = { }); }, Get: function (callBack) { - $.post("/Basket/GetBaskets", function (data) { + $.post("/Basket/GetBaskets", Object.assign({}, ZKEACMS.AntiToken), function (data) { if (callBack) { callBack.call(data); } }); }, ShowBasket: function () { - $.post("/Basket/Index", function (data) { + $.post("/Basket/Index", Object.assign({}, ZKEACMS.AntiToken), function (data) { if (data.location) { window.location = data.location; } else { @@ -123,7 +125,7 @@ $(function () { }); }); $(document).on("click", ".basket .ckeck-out", function () { - $.post("/Basket/CheckOut", function (data) { + $.post("/Basket/CheckOut", Object.assign({}, ZKEACMS.AntiToken), function (data) { if (data.location) { window.location = data.location; } else { diff --git a/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.min.js b/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.min.js index 7216ffe5..a27f6d57 100644 --- a/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.min.js +++ b/src/ZKEACMS.Shop/Scripts/ZKEACMS.Basket.min.js @@ -1,4 +1,4 @@ /*! http://www.zkea.net/ * Copyright (c) ZKEASOFT. All rights reserved. * http://www.zkea.net/licenses */ -var ZKEACMS=ZKEACMS||{};ZKEACMS.Basket={Add:function(n,t){$.post("/Basket/Add",n,function(n){n.location?window.location=n.location:t&&t.call(n)})},Update:function(n,t){$.post("/Basket/Update",n,function(n){n.location?window.location=n.location:t&&t.call(n)})},Remove:function(n,t){$.post("/Basket/Remove",{basketId:n},function(n){n.location?window.location=n.location:t&&t.call(n)})},Get:function(n){$.post("/Basket/GetBaskets",function(t){n&&n.call(t)})},ShowBasket:function(){$.post("/Basket/Index",function(n){if(n.location)window.location=n.location;else{var i=$("body>.basket"),t=$(n);i.length>0?(t.addClass("active"),i.replaceWith(t)):($("body").append(t),setTimeout(function(){t.addClass("active")},10))}})}};$(function(){function t(t){n.length>0&&(t.data&&t.data.quantity>0?(n.children().addClass("active"),n.children(".quantity").text(t.data.quantity)):(n.children().removeClass("active"),n.children(".quantity").text("")))}$(document).on("click",".basket .close",function(){$("body>.basket").removeClass("active");setTimeout(function(){$("body>.basket").remove()},300)});$(document).on("click",".add-to-basket",function(){var i=$(this).data("tags"),n;i||(n=[],$(this).closest(".product-ecommerce").find("input[type=radio]:checked").each(function(){n.push($(this).val())}),n.length>0&&(i=n.join(";")));ZKEACMS.Basket.Add({productId:$(this).data("productid"),quantity:$(this).data("quantity"),tags:i},function(){ZKEACMS.Basket.ShowBasket();t(this)})});$(document).on("click",".basket .quantity-minus",function(){var n=Number($.trim($(this).closest(".quantity-set").find(".quantity").text())),i,r;n>1&&(n-=1,i=$(this).closest(".basket-item"),i.find(".quantity").text(n),r=i.data("id"),Easy.Processor(function(){ZKEACMS.Basket.Update({basketId:r,quantity:n},function(){$(".basket .total-items").text(this.data.quantity);$(".basket .total-price").text(this.data.total.toFixed(2));t(this)})},300))});$(document).on("click",".basket .quantity-plus",function(){var n=Number($.trim($(this).closest(".quantity-set").find(".quantity").text())),i,r;n+=1;i=$(this).closest(".basket-item");i.find(".quantity").text(n);r=i.data("id");Easy.Processor(function(){ZKEACMS.Basket.Update({basketId:r,quantity:n},function(){$(".basket .total-items").text(this.data.quantity);$(".basket .total-price").text(this.data.total.toFixed(2));t(this)})},300)});$(document).on("click",".basket .remove",function(){var n=$(this).closest(".basket-item").data("id");ZKEACMS.Basket.Remove(n,function(){$(".basket #basket-"+n).remove();$(".basket .basket-body>ul>li").length==0&&($(".basket .basket-body>ul").append('
  • 您的购物车是空的<\/li >'),$(".basket .basket-footer").remove());t(this)})});$(document).on("click",".basket .ckeck-out",function(){$.post("/Basket/CheckOut",function(n){if(n.location)window.location=n.location;else{var i=$("body>.basket"),t=$(n);i.length>0?(t.addClass("active"),i.replaceWith(t)):($("body").append(t),setTimeout(function(){t.addClass("active")},10))}},"html")});$(document).on("click",".basket .confirm-order",function(){$("#PaymentGateway").val($(this).data("payment"));$(this).closest("form").submit()});var n=$(".navigation .show-basket");n.length>0&&(ZKEACMS.Basket.Get(function(){t(this)}),n.click(ZKEACMS.Basket.ShowBasket))}); \ No newline at end of file +var ZKEACMS=ZKEACMS||{};ZKEACMS.Basket={Add:function(n,t){Object.assign(n,ZKEACMS.AntiToken);$.post("/Basket/Add",n,function(n){n.location?window.location=n.location:t&&t.call(n)})},Update:function(n,t){Object.assign(n,ZKEACMS.AntiToken);$.post("/Basket/Update",n,function(n){n.location?window.location=n.location:t&&t.call(n)})},Remove:function(n,t){$.post("/Basket/Remove",Object.assign({basketId:n},ZKEACMS.AntiToken),function(n){n.location?window.location=n.location:t&&t.call(n)})},Get:function(n){$.post("/Basket/GetBaskets",Object.assign({},ZKEACMS.AntiToken),function(t){n&&n.call(t)})},ShowBasket:function(){$.post("/Basket/Index",Object.assign({},ZKEACMS.AntiToken),function(n){if(n.location)window.location=n.location;else{var i=$("body>.basket"),t=$(n);i.length>0?(t.addClass("active"),i.replaceWith(t)):($("body").append(t),setTimeout(function(){t.addClass("active")},10))}})}};$(function(){function t(t){n.length>0&&(t.data&&t.data.quantity>0?(n.children().addClass("active"),n.children(".quantity").text(t.data.quantity)):(n.children().removeClass("active"),n.children(".quantity").text("")))}$(document).on("click",".basket .close",function(){$("body>.basket").removeClass("active");setTimeout(function(){$("body>.basket").remove()},300)});$(document).on("click",".add-to-basket",function(){var i=$(this).data("tags"),n;i||(n=[],$(this).closest(".product-ecommerce").find("input[type=radio]:checked").each(function(){n.push($(this).val())}),n.length>0&&(i=n.join(";")));ZKEACMS.Basket.Add({productId:$(this).data("productid"),quantity:$(this).data("quantity"),tags:i},function(){ZKEACMS.Basket.ShowBasket();t(this)})});$(document).on("click",".basket .quantity-minus",function(){var n=Number($.trim($(this).closest(".quantity-set").find(".quantity").text())),i,r;n>1&&(n-=1,i=$(this).closest(".basket-item"),i.find(".quantity").text(n),r=i.data("id"),Easy.Processor(function(){ZKEACMS.Basket.Update({basketId:r,quantity:n},function(){$(".basket .total-items").text(this.data.quantity);$(".basket .total-price").text(this.data.total.toFixed(2));t(this)})},300))});$(document).on("click",".basket .quantity-plus",function(){var n=Number($.trim($(this).closest(".quantity-set").find(".quantity").text())),i,r;n+=1;i=$(this).closest(".basket-item");i.find(".quantity").text(n);r=i.data("id");Easy.Processor(function(){ZKEACMS.Basket.Update({basketId:r,quantity:n},function(){$(".basket .total-items").text(this.data.quantity);$(".basket .total-price").text(this.data.total.toFixed(2));t(this)})},300)});$(document).on("click",".basket .remove",function(){var n=$(this).closest(".basket-item").data("id");ZKEACMS.Basket.Remove(n,function(){$(".basket #basket-"+n).remove();$(".basket .basket-body>ul>li").length==0&&($(".basket .basket-body>ul").append('
  • 您的购物车是空的<\/li >'),$(".basket .basket-footer").remove());t(this)})});$(document).on("click",".basket .ckeck-out",function(){$.post("/Basket/CheckOut",Object.assign({},ZKEACMS.AntiToken),function(n){if(n.location)window.location=n.location;else{var i=$("body>.basket"),t=$(n);i.length>0?(t.addClass("active"),i.replaceWith(t)):($("body").append(t),setTimeout(function(){t.addClass("active")},10))}},"html")});$(document).on("click",".basket .confirm-order",function(){$("#PaymentGateway").val($(this).data("payment"));$(this).closest("form").submit()});var n=$(".navigation .show-basket");n.length>0&&(ZKEACMS.Basket.Get(function(){t(this)}),n.click(ZKEACMS.Basket.ShowBasket))}); \ No newline at end of file diff --git a/src/ZKEACMS.WebHost/DefaultResourceManager.cs b/src/ZKEACMS.WebHost/DefaultResourceManager.cs index de0db871..a5b416c1 100644 --- a/src/ZKEACMS.WebHost/DefaultResourceManager.cs +++ b/src/ZKEACMS.WebHost/DefaultResourceManager.cs @@ -103,6 +103,10 @@ protected override void InitScript(Func script) script("list-editor") .Include($"{LibraryPath}/jquery-ui/jquery-ui-sortable.js", $"{LibraryPath}/jquery-ui/jquery-ui-sortable.min.js") .Include($"{ScriptPath}/list-editor.js", $"{ScriptPath}/list-editor.min.js"); + + script("Antiforgery") + .Include("~/js/antiforgery/tokenset.js") + .RequiredAtFoot(); } protected override void InitStyle(Func style) diff --git a/src/ZKEACMS/Controllers/AntiforgeryController.cs b/src/ZKEACMS/Controllers/AntiforgeryController.cs new file mode 100644 index 00000000..3ece6529 --- /dev/null +++ b/src/ZKEACMS/Controllers/AntiforgeryController.cs @@ -0,0 +1,25 @@ +/* http://www.zkea.net/ + * Copyright (c) ZKEASOFT. All rights reserved. + * http://www.zkea.net/licenses */ + +using Microsoft.AspNetCore.Antiforgery; +using Microsoft.AspNetCore.Mvc; + +namespace ZKEACMS.Controllers +{ + public class AntiforgeryController : Controller + { + private readonly IAntiforgery _antiforgery; + + public AntiforgeryController(IAntiforgery antiforgery) + { + _antiforgery = antiforgery; + } + public IActionResult GetTokenSet() + { + var tokenSet = _antiforgery.GetAndStoreTokens(HttpContext); + string content = $"/*! http://www.zkea.net/ Copyright (c) ZKEASOFT. All rights reserved. http://www.zkea.net/licenses */ var ZKEACMS = ZKEACMS || {{}}; ZKEACMS.AntiToken = {{ '{tokenSet.FormFieldName}': '{tokenSet.RequestToken}' }};"; + return Content(content, "application/javascript"); + } + } +} diff --git a/src/ZKEACMS/Route/RouteDescriptors.cs b/src/ZKEACMS/Route/RouteDescriptors.cs index 3bcf40d8..eda0d71f 100644 --- a/src/ZKEACMS/Route/RouteDescriptors.cs +++ b/src/ZKEACMS/Route/RouteDescriptors.cs @@ -37,6 +37,13 @@ public static class RouteDescriptors Priority = 11 }, new RouteDescriptor + { + RouteName = "Antiforgery", + Template = "js/antiforgery/tokenset.js", + Defaults = new { controller = "Antiforgery",action="GetTokenSet" }, + Priority = 11 + }, + new RouteDescriptor { RouteName = "error", Template = "error/{action}/{code?}",