一般采用表单验证完成登陆验证,建议结合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小时内与您取得联系。