一般采用表單驗證完成登陸驗證,建議結合SSL使用。為限制控制器只能執行HTTPS,使用RequireHttpsAttribute
對賬戶的權限的控制可以通過在控制器或控制器操作上加AuthorizeAttribute 屬性。
擴展授權過濾器可以定義繼承自AuthorizeAttribute的類,也可以定義同時繼承自FilterAttribute, IAuthorizationFilter接口的類。
擴展AuthorizeAttribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited=true, AllowMultiple=true)] public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter { public AuthorizeAttribute(); // 獲取或設置有權訪問控制器或操作方法的用戶角色 public string Roles { get; set; } //獲取此特性的唯一標識符。 public override object TypeId { get; } // 獲取或設置有權訪問控制器或操作方法的用戶。 public string Users { get; set; } //重寫時,提供一個入口點用于進行自定義授權檢查 // 返回結果: // 如果用戶已經過授權,則為 true;否則為 false。 // 異常: // System.ArgumentNullException: // httpContext 參數為 null。 protected virtual bool AuthorizeCore(HttpContextBase httpContext); //處理未能授權的 HTTP 請求。 protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext); //在過程請求授權時調用。 // 異常: // System.ArgumentNullException: // filterContext 參數為 null。 public virtual void OnAuthorization(AuthorizationContext filterContext); // // 返回結果: // 對驗證狀態的引用。 // // 異常: // System.ArgumentNullException: // httpContext 參數為 null。 protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext); }
AuthorizeAttribute提供了三個可重新的虛方法AuthorizeCore,HandleUnauthorizedRequest,OnAuthorization,那么在執行授權動作的過程中他們是如何被調用的呢?
看下源碼的OnAuthorization方法,發現在這個方法中先調用AuthorizeCore,然后調用HandleUnauthorizedRequest被調用了。
public virtual void OnAuthorization(AuthorizationContext filterContext) { if (filterContext==null) { throw new ArgumentNullException("filterContext"); } //如果子操作的緩存處于活動狀態,那么就拋出異常 if (OutputCacheAttribute.IsChildActionCacheActive(filterContext)) { throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache); }
//判斷控制器或控制器操作是否允許匿名訪問,如果可以就return
bool skipAuthorization=filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true); if (skipAuthorization) { return; } //進行權限驗證 if (AuthorizeCore(filterContext.HttpContext)) { HttpCachePolicyBase cachePolicy=filterContext.HttpContext.Response.Cache; cachePolicy.SetProxyMaxAge(new TimeSpan(0)); cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */); } else {//處理未通過權限驗證的情形 HandleUnauthorizedRequest(filterContext); } }
綜合以上分析,擴展AuthorizeAttribute要注意:
1)在子類AuthorizeCore中,調用父類的AuthorizeCore方法
base.OnAuthorization(filterContext);
2)在子類的AuthorizeCore方法中驗證用戶的權限。
3)通過子類的構造函數傳入用戶的權限值
代碼示例如下:
public class CustomAuthorizeAttribute : AuthorizeAttribute { private UserRole role; public CustomAuthorizeAttribute(UserRole role) { this.role=role; } protected override bool AuthorizeCore(HttpContextBase httpContext) { bool ret=false; //獲得用戶信息(從本地Session或分布式緩存中獲取) var userInfo=...... if(userInfo==null) { //信息為null,一般認為登陸超時或沒有登陸 } if(userInfo.Role==UserRole.Org) { ret=true; } else { //提示無權限 } return ret; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { if (filterContext==null) { throw new ArgumentNullException("filterContext"); } if (filterContext.HttpContext.Request.IsAjaxRequest()) {//針對ajax請求進行處理 } else {//非aiax進行處理 //跳轉到指定頁面 string strUrl=......; filterContext.Result=new RedirectResult(strUrl); } } public override void OnAuthorization(AuthorizationContext filterContext) { base.OnAuthorization(filterContext); } } public enum UserRole { Org=1, Vip=2, Guest=3 }
總的原則:
被動注入:用戶的輸入含有惡意腳本,而網站又能夠不加檢驗地接受這樣的輸入,進而保存到數據庫中。
主動注入:用戶將含有惡意腳本的內容輸入到頁面文本框中,然后在屏幕上顯示出來。
防御方法:
1)使用Razor語法輸出的內容已經被編碼,可以不做任何其他處理
例如:
<h4>@Model.Field</h4>
Html.ActionLink,Html.Action等方法會將路由參數編碼輸出
2)大部分的XSS攻擊可通過對輸入內容進行編碼來阻止:Html.Encode,Html.AttributeEncode,Url.Encode
3)對Js進行編碼
使用Ajax.JavaScriptStringEncode
4)將AntiXSS庫作為默認的編碼器(不建議使用,不靈活)
ASP.NET 4.5 集成Anti-XSS Library,可以通過配置來對整個網站的輸出進行編碼。
<system.web> <httpRuntime targetFramework="4.5" encoderType="System.Web.Security.AntiXss.AntiXssEncoder,System.Web"/> </system.web>
防御方法:
1)使用Html隱藏域存儲用戶令牌,令牌可以存儲在Session里或者cookie里
2)在視圖表單中使用@Html.AntiForgeryToken(),在控制器操作上添加屬性[ValidateAntiForgeryToken],注意表單一定要使用@Html.BeginForm生成
實現機制:AntiForgeryToken方法向用戶瀏覽器cookie中寫入一個加密的數據,并在表單內插入一個隱藏欄位,每次刷新頁面時隱藏欄位的值都不同,每次執行控制器操作前,都會驗證隱藏欄位和瀏覽器cookie中的值是否相同,只有相同才允許執行控制器操作。
使用限制:
3)使用冪等的Get請求,僅使用Post請求修改數據(僅僅是一定程度上限制這種攻擊而已)
4)使用動作過濾器,驗證UrlReferrer
擴展的動作過濾器:
public class CSRFFilter:AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { if (filterContext.HttpContext==null) { throw new HttpException("請求無效"); } if (filterContext.HttpContext.Request.UrlReferrer==null) { throw new HttpException("請求無效"); } if (filterContext.HttpContext.Request.UrlReferrer.Host !="sit.com") { throw new HttpException("來自非法網站"); } } }
cookie有兩種形式
1)會話cookie:存儲在瀏覽器內存中,瀏覽器每次請求通過Http頭進行傳遞
2)持久性cookie:存儲在硬盤上,同樣通過Http頭進行傳遞
二者的區別:會話cookie常在會話結束時失效,而持久性cookie在下一次訪問站點時仍然有效。
被竊取的原因:依賴于XSS漏洞,注入一段惡意腳本就能竊取。
防御方法:
1)在web.config對cookie進行設置
<httpCookies httpOnlyCookies="true"/>,httpOnlyCookies指定為true表達僅服務器可以訪問,瀏覽器無法訪問
2)在編寫代碼時為每個cookie單獨設置
Response.Cookies["cok"].Value=Guid.NewGuid().ToString();
Response.Cookies["cok"].HttpOnly=true;
防御方法:
1)使用bind特性,設置想要綁定的屬性來,防止這種攻擊。也可以設置不要綁定的字屬性,但優先選擇設置要綁定的屬性。
例:
可以指定多個字段,用逗號分隔
public ActionResult TestViewData([Bind(Include="Field,Field1,Field1")]ModelF mf) { ...... }
2)使用UpdateModel或TryUpdateModel
3)使用ViewModel,明確規定View使用的數據模型
防御方法:
使用Url.IsLocalUrl檢測是否為本地url
防御方法:
通過參數注入非法獲得或修改網站數據。
使用參數化查詢來防止SQL注入攻擊。
這篇文章中,Web開發人員和DZone MVB介紹了如何通過驗證數組來啟用和禁用一組元素的技術。
今天,我們介紹如何通過驗證數組來啟用和禁用一組元素的技術。
當涉及到前端的復雜業務邏輯時,Web應用程序變得越來越難以編寫。
根據復雜性,可能需要您在提交之前接受一組輸入并驗證這些元素。
我提交的最近一個屏幕需要多個DOM元素在啟用提交或“操作”按鈕之前處于特定狀態。
當一組DOM元素處于特定狀態時,我們希望觸發其他元素。
我知道你在想什么。為什么不使用jQuery進行驗證?
原因如下:
對于這個任務,我們需要jQuery和不顯眼的驗證庫。這是我們真正不需要的兩個大型圖書館。
借助最新的JavaScript特性,您會驚訝于瀏覽器中已有多少功能,以及您甚至可能不再需要jQuery。
我想要一個快速和整潔的解決方案來解決這個問題。
據說,我著手尋找一種更簡單的方式來有條件地驗證DOM元素組。
讓我們用一個網格來設置一個簡單的場景。
Views/Home/Index.cshtml
@model EventViewModel
@{
ViewData["Title"]="Grouped Validations";
}
<style>
th:nth-child(1) {
width: 20px
}
</style>
<h2>Events</h2>
@using (Html.BeginForm())
{
<div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups">
<div class="btn-group mr-2" role="group" aria-label="First group">
<button id="enabled-button" type="button" disabled="disabled" class="btn btn-info disabled">Enable</button>
<button id="disabled-button" type="button" disabled="disabled" class="btn btn-info disabled">Disable</button>
</div>
</div>
<table class="table table-condensed table-bordered table-striped">
<thead>
<tr>
<th><input type="checkbox" id="select-all" /></th>
<th>Event</th>
<th>Begin Date</th>
<th>End Date</th>
</tr>
</thead>
<tbody>
@foreach (var eventModel in Model.Events)
{
<tr>
<td><input name="select" class="event-checkbox" type="checkbox" value="@eventModel.EventId" /></td>
<td><a title="Go to @eventModel.Title" href="@eventModel.Url.AbsoluteUri">@eventModel.Title</a></td>
<td>@eventModel.BeginDate.ToShortDateString()</td>
<td>@eventModel.EndDate.ToShortDateString()</td>
</tr>
}
</tbody>
</table>
}
這個網格在左邊有復選框,帶有批按鈕來啟用和禁用事件。點擊標題中的復選框將選擇所有這些選項。
首先,我們需要受用戶操作影響的元素。
this.enabledButton=document.getElementById("enabled-button");
this.disabledButton=document.getElementById("disabled-button");
接下來,我們需要我們的驗證。這些驗證存儲在包含以下項目的數組中:
要啟用/禁用的元素。
一個簡單的條件。
條件為真時的lambda。
條件為false時的lambda。
我們的驗證數組看起來像這樣:
// Custom validations for enabling/disabling DOM elements based on conditions.
this.validations=[
{
// When should the enable button be active?
element: this.enabledButton,
condition: ()=> {
var checkedCheckboxes=areChecked();
var valid=(
// Do we have even one checkbox selected?
checkedCheckboxes.length > 0
);
return valid;
},
trueAction: (elem)=> {
elem.removeAttribute("disabled");
elem.classList.remove("disabled");
},
falseAction: (elem)=> {
elem.setAttribute("disabled", "disabled");
elem.classList.add("disabled");
}
},
// Second validation
{
// When should the disable button be active?
element: this.disabledButton,
condition: ()=> {
var checkedCheckboxes=areChecked();
var valid=(
// No checkboxes are available, time to disable.
checkedCheckboxes.length > 0
);
return valid;
},
trueAction: (elem)=> {
elem.removeAttribute("disabled");
elem.classList.remove("disabled");
},
falseAction: (elem)=> {
elem.setAttribute("disabled", "disabled");
elem.classList.add("disabled");
}
}
];
我們基本上建立了一個“編碼陣列”。這個數組擁有我們編寫數據驅動代碼所需的一切。
如果您發現 trueAction并且 falseAction,我們正在建立的拉姆達接收的元素。一旦我們收到元素,我們就會將某些屬性應用到DOM元素,當它是真或假操作時。
我們的條件可以像我們需要的那樣復雜,以啟用或禁用某些DOM元素。
為了使我們的DOM元素可用或禁用,我們需要遍歷我們的驗證并對其執行操作; 所以,我們將使用該 map功能。
function updateValidations() {
Array.from(validations).map( (item, index, array)=> {
if (item.condition()) {
item.trueAction(item.element);
} else {
item.falseAction(item.element);
}
});
}
一旦我們有我們的功能來啟用或禁用DOM元素,我們需要將我們的事件附加到復選框。
// Set the "select all" checkbox.
var checkAll=document.getElementById("select-all");
checkAll.addEventListener("change", checkAllCheckbox);
我們的變化事件 checkAllCheckbox當然會稱為我們的 updateValidations();功能。
function checkAllCheckbox() {
var allCheckbox=document.getElementById("select-all");
var eventCheckboxes=Array.from(
document.getElementsByClassName("event-checkbox")
);
eventCheckboxes.map( (elem, index, array)=> {
elem.checked=allCheckbox.checked;
});
updateValidations();
}
所有這些都不使用jQuery或驗證庫。
當然,總會遇到一些......呃......某些瀏覽器。
一些用戶報告了復選框單擊不與其他復選框一起工作的問題。
我想知道=>舊版瀏覽器的arrow()語法。
所以我去了caniuse.com以確認這是否可以與其他瀏覽器一起使用。
你不知道嗎?我去了箭頭語法,發現這個:caniuse.com/#search=arrow
由于我們不能使用箭頭語法,所以修復非常簡單。
而不是這樣做:
{
// When should the enable button be active?
element: this.enabledButton,
condition: ()=> {
var checkedCheckboxes=areChecked();
var valid=(
// Do we have even one checkbox selected?
checkedCheckboxes.length > 0
);
return valid;
},
trueAction: (elem)=> {
elem.removeAttribute("disabled");
elem.classList.remove("disabled");
},
falseAction: (elem)=> {
elem.setAttribute("disabled", "disabled");
elem.classList.add("disabled");
}
},
我們做得到:
{
// When should the enable button be active?
element: this.enabledButton,
condition: function () {
var checkedCheckboxes=areChecked();
var valid=(
// Do we have even one checkbox selected?
checkedCheckboxes.length > 0
);
return valid;
},
trueAction: function (elem) {
elem.removeAttribute("disabled");
elem.classList.remove("disabled");
},
falseAction: function (elem) {
elem.setAttribute("disabled", "disabled");
elem.classList.add("disabled");
}
},
請記住,拉姆達只是匿名代表。用一個簡單的函數替換箭頭語法可以實現向后兼容性的奇跡。
對于項目來源,請查看我的GitHub存儲庫。
一旦建立起來,我就可以在多個頁面上使用這個功能,以便“引導”用戶在屏幕上可以或不可以執行的操作。當一個DOM元素被啟用時,它觸發另外兩個元素供用戶在屏幕上進行交互。
這可以很容易地修改為使用提交按鈕。如果滿足所有這些條件,請啟用提交按鈕。如果不是,則禁用它直到滿足所有條件。
再次,您可以根據需要將其設置為復雜或簡單。
、點擊引用右鍵,管理NuGet程序包,輸入NPIO,選擇紅框選中的下載
2、創建一個 ExcelController 控制器,將Index 方法生成視圖
3、批量導入excel的頁面源碼:
<h2>批量導入excel</h2>
<div>
@using (Html.BeginForm("Import", "Excel", FormMethod.Post, new { enctype="multipart/form-data" }))
{
<input name="files" type="file" multiple="multiple" id="=file" />
<br />
<br />
<input name="submit" id="submit" type="submit" value="批量導入" />
}
</div>
這里需要注意:file 的multiple屬性是能多個文件選中,enctype="multipart/form-data" 是上傳必須的表單屬性
4、在ExcelController 控制器里面添加方法
[HttpPost]
public ActionResult Import(IEnumerable<HttpPostedFileBase> files)
{
// var fileName=file.FileName;
var filePath=Server.MapPath(string.Format("~/{0}", "Files"));
foreach (var file in files)
{
if (file !=null && file.ContentLength > 0)
{
var path=Path.Combine(filePath, file.FileName);
file.SaveAs(path);
DataTable excelTable=new DataTable();
excelTable=ImportExcel.GetExcelDataTable(path);
DataTable dbdata=new DataTable();
dbdata.Columns.Add("ids");
dbdata.Columns.Add("users");
dbdata.Columns.Add("area");
dbdata.Columns.Add("school");
dbdata.Columns.Add("classes");
dbdata.Columns.Add("name");
dbdata.Columns.Add("phone");
dbdata.Columns.Add("integration");
dbdata.Columns.Add("states");
dbdata.Columns.Add("createDate");
dbdata.Columns.Add("refreshDate");
for (int i=0; i < excelTable.Rows.Count; i++)
{
DataRow dr=excelTable.Rows[i];
DataRow dr_=dbdata.NewRow();
dr_["ids"]=dr["ID"];
dr_["users"]=dr["用戶"];
dr_["area"]=dr["區域"];
dr_["school"]=dr["學校"];
dr_["classes"]=dr["班級"];
dr_["name"]=dr["姓名"];
dr_["phone"]=dr["手機"];
dr_["integration"]=dr["積分"];
dr_["states"]=dr["狀態"];
dr_["createDate"]=dr["創建時間"];
dr_["refreshDate"]=dr["更新時間"];
dbdata.Rows.Add(dr_);
}
RemoveEmpty(dbdata);
string constr=System.Configuration.ConfigurationManager.AppSettings["cool"];
SqlBulkCopyByDatatable(constr, "student", dbdata);
}
}
return RedirectToAction("Index", "DataRefsh");
}
/// <summary>
/// 大數據插入
/// </summary>
/// <param name="connectionString">目標庫連接</param>
/// <param name="TableName">目標表</param>
/// <param name="dtSelect">來源數據</param>
public static void SqlBulkCopyByDatatable(string connectionString, string TableName, DataTable dtSelect)
{
using (SqlConnection conn=new SqlConnection(connectionString))
{
using (SqlBulkCopy sqlbulkcopy=new SqlBulkCopy(connectionString, SqlBulkCopyOptions.UseInternalTransaction))
{
try
{
sqlbulkcopy.DestinationTableName=TableName;
sqlbulkcopy.BatchSize=20000;
sqlbulkcopy.BulkCopyTimeout=0;//不限時間
for (int i=0; i < dtSelect.Columns.Count; i++)
{
sqlbulkcopy.ColumnMappings.Add(dtSelect.Columns[i].ColumnName, dtSelect.Columns[i].ColumnName);
}
sqlbulkcopy.WriteToServer(dtSelect);
}
catch (System.Exception ex)
{
throw ex;
}
}
}
}
protected void RemoveEmpty(DataTable dt)
{
List<DataRow> removelist=new List<DataRow>();
for (int i=0; i < dt.Rows.Count; i++)
{
bool IsNull=true;
for (int j=0; j < dt.Columns.Count; j++)
{
if (!string.IsNullOrEmpty(dt.Rows[i][j].ToString().Trim()))
{
IsNull=false;
}
}
if (IsNull)
{
removelist.Add(dt.Rows[i]);
}
}
for (int i=0; i < removelist.Count; i++)
{
dt.Rows.Remove(removelist[i]);
}
}
這里需要注意:IEnumerable<HttpPostedFileBase> 是讀取多個文件的名稱,HttpPostedFileBase只能讀取一個,還有就是files 是file 控件name 相對應的
5、string constr=System.Configuration.ConfigurationManager.AppSettings["cool"]; 需要在web.config 里面的appSettings 里面添加數據庫連接字符串的配置
6、控制器import 方法里面有用到GetExcelDataTable 和GetCellValue 方法
這里創建一個ImportExcel 類,創建以下兩個方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NPOI;
using System.Data;
using NPOI.SS.UserModel;
using NPOI.HSSF.UserModel;
using NPOI.XSSF.UserModel;
using System.IO;
namespace Cool{
public class ImportExcel
{
public static DataTable GetExcelDataTable(string filePath)
{
IWorkbook Workbook;
DataTable table=new DataTable();
try
{
using (FileStream fileStream=new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
//XSSFWorkbook 使用XLSX格式,HSSFWorkbook 使用XLS格式
string fileExt=Path.GetExtension(filePath).ToLower();
if (fileExt==".xls")
{
Workbook=new HSSFWorkbook(fileStream);
}
else if (fileExt==".xlsx")
{
Workbook=new XSSFWorkbook(fileStream);
}
else
{
Workbook=null;
}
}
}
catch (Exception ex)
{
throw ex;
}
//定位在第一個sheet
ISheet sheet=Workbook.GetSheetAt(0);
//第一行為標題行
IRow headerRow=sheet.GetRow(0);
int cellCount=headerRow.LastCellNum;
int rowCount=sheet.LastRowNum;
//循環添加標題列
for (int i=headerRow.FirstCellNum; i < cellCount; i++)
{
DataColumn column=new DataColumn(headerRow.GetCell(i).StringCellValue);
table.Columns.Add(column);
}
//數據
for (int i=(sheet.FirstRowNum + 1); i <=rowCount; i++)
{
IRow row=sheet.GetRow(i);
DataRow dataRow=table.NewRow();
if (row !=null)
{
for (int j=row.FirstCellNum; j < cellCount; j++)
{
if (row.GetCell(j) !=null)
{
dataRow[j]=GetCellValue(row.GetCell(j));
}
}
}
table.Rows.Add(dataRow);
}
return table;
}
private static string GetCellValue(ICell cell)
{
if (cell==null)
{
return string.Empty;
}
switch (cell.CellType)
{
case CellType.Blank:
return string.Empty;
case CellType.Boolean:
return cell.BooleanCellValue.ToString();
case CellType.Error:
return cell.ErrorCellValue.ToString();
case CellType.Numeric:
case CellType.Unknown:
default:
return cell.ToString();
case CellType.String:
return cell.StringCellValue;
case CellType.Formula:
try
{
HSSFFormulaEvaluator e=new HSSFFormulaEvaluator(cell.Sheet.Workbook);
e.EvaluateInCell(cell);
return cell.ToString();
}
catch
{
return cell.NumericCellValue.ToString();
}
}
}
}}
這里需要注意:NPOI 只支持93-2007的offcie 的excel文件,如果是高版本的excel文件,需要降級,打開excel文件,另存為93-2007 的.xls 文件即可
這樣就可以批量把文件數據導入到數據庫中了!!!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。