分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > IT知识

跟我学ASP.NET MVC之十:SportsStrore安全

发布时间:2023-09-06 01:56责任编辑:熊小新关键词:.NETMVC

摘要:

在之前的文章中,我给SportsStore应用程序添加了产品管理功能,这样一旦我发布了网站,任何人都可能修改产品信息,而这是你必须考虑的。他们只需要知道你的网站有这个功能,以及功能的访问路径是/Admin/Index。我将向你介绍如何通过对Admin控制器实现密码保护来防止任意的人员使用管理功能。

创建基本安全策略

我将从配置表单身份验证开始,它是用户在ASP.NET应用程序身份验证的一种方式。修改Web.config文件的System.Web节,添加authentication子节点。

1 ??<system.web>2 ????<compilation debug="true" targetFramework="4.5.1" />3 ????<httpRuntime targetFramework="4.5.1" />4  ???<authentication mode="Forms">5 ?? ???<forms loginUrl="~/Account/Login" timeout="2880" >6 ?? ???</forms>7 ?? ?</authentication>8 ??</system.web>

该配置表示,使用表单验证。如果表单验证失败,则网页定向到/Account/Login页面,验证有效期时间是2880分钟(48小时)。

还有其他的身份验证方式,另一个常用的是Windows方式验证。它使用User Group或者Active Directory验证。这里不打算介绍它。读者可以在网上搜索这方面的知识。

还可以给该表单验证配置添加credential。

 1 ??<system.web> 2 ????<compilation debug="true" targetFramework="4.5.1" /> 3 ????<httpRuntime targetFramework="4.5.1" /> 4 ????<authentication mode="Forms"> 5 ??????<forms loginUrl="~/Account/Login" timeout="2880" > 6  ???????<credentials passwordFormat="Clear"> 7 ? ????????<user name="user" password="123"/> 8 ???? ???</credentials> 9 ??????</forms>10 ????</authentication>11 ??</system.web>

该credentials配置表示,在配置文件中使用密码明文(不推荐),登录用户名是user,密码是123。

使用过滤器应用表单验证

MVC框架有一个强大的名叫过滤器的功能。他们是你可以运用在Action方法或者控制器类上的.NET特性,当一个请求发送过来将改变MVC框架行为的时候,引入额外的业务逻辑。

这里我将把它修饰AdminController控制器类,它将给该控制器内的所有Action方法添加这个过滤器。

1  ???[Authorize]2 ????public class AdminController : Controller3 ????{4 ????????private IProductRepository repository;5 6 ????????public AdminController(IProductRepository productRepository)7 ????????{8 ????????????repository = productRepository;9 ????????}

如果将该过滤器应用到Action方法里,则只对这个Action起作用。

创建表单验证方法

有了表单验证配置和表单验证过滤器之后,还需要定义表单验证的逻辑方法。

 首先定义一个接口IAuthProvider。

1 namespace SportsStore.Infrastructure.Abstract2 {3 ????public interface IAuthProvider4 ????{5 ????????bool Authenticate(string userName, string password);6 ????}7 }

该接口只定义了一个接口方法Authenticate,根据传入的用户名和密码,返回验证是否成功的布尔值。

然后,实现接口IAuthProvider的类FormsAuthProvider 。

 1 using SportsStore.Infrastructure.Abstract; 2 using System.Web.Security; 3 ?4 namespace SportsStore.WebUI.Infrastructure.Concrete 5 { 6 ????public class FormsAuthProvider : IAuthProvider 7 ????{ 8 ????????public bool Authenticate(string userName, string password) 9 ????????{10 ????????????bool result = FormsAuthentication.Authenticate(userName, password);11 ????????????if (result)12 ????????????{13 ????????????????FormsAuthentication.SetAuthCookie(userName, false);14 ????????????}15 ????????????return result;16 ????????}17 ????}18 }

这里将调用静态函数FormsAuthentication.Authenticate进行表单验证。如果Web.config文件中定义了credentials配置,则使用配置文件中定义的用户名和密码进行验证。

如果验证成功,则调用另一个静态函数FormsAuthentication.SetAuthCookie向客户端写入用户名userName字符串的cookie。

还需要将实现类FormsAuthProvider绑定到接口IAuthProvider。

修改类NinjectDependencyResolver的方法AddBindings,添加Ninject绑定。

 1 ????????private void AddBindings() 2 ????????{ 3 ????????????kernel.Bind<IProductRepository>().To<EFProductRepository>(); 4 ?5 ????????????EmailSettings emailSettings = new EmailSettings 6 ????????????{ 7 ????????????????WriteAsFile = bool.Parse(System.Configuration.ConfigurationManager.AppSettings["Email.WriteAsFile"] ?? "false") 8 ????????????}; 9 ????????????kernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>().WithConstructorArgument("settings", emailSettings);10 11 ??????????? kernel.Bind<IAuthProvider>().To<FormsAuthProvider>();12 ????????}

定义LoginViewModel

 1 using System.ComponentModel.DataAnnotations; 2 ?3 namespace SportsStore.WebUI.Models 4 { 5 ????public class LoginViewModel 6 ????{ 7 ????????[Display(Name = "User Name")] 8 ????????[Required(ErrorMessage = "Please enter a user name")] 9 ????????public string UserName { get; set; }10 ????????[Required(ErrorMessage = "Please enter a password")]11  ???????[DataType(DataType.Password)]12 ????????public string Password { get; set; }13 ????}14 }

这个视图模型类只有用户名和密码属性。它们都加了Required验证特性。Password属性加了DataType特性,这样自动生成的表单password输入框元素将是一个password输入框(输入的文本内容不可见)。

创建Account控制器

 1 using SportsStore.Infrastructure.Abstract; 2 using SportsStore.WebUI.Models; 3 using System.Web.Mvc; 4 ?5 namespace SportsStore.Controllers 6 { 7 ????public class AccountController : Controller 8 ????{ 9 ????????IAuthProvider authProvider;10 11 ????????public AccountController(IAuthProvider authProvidor)12 ????????{13 ????????????authProvider = authProvidor;14 ????????}15 16 ????????public ActionResult Login()17 ????????{18 ????????????return View();19 ????????}20 21 ????????[HttpPost]22 ????????public ActionResult Login(LoginViewModel model, string returnUrl)23 ????????{24 ????????????if (ModelState.IsValid)25 ????????????{26 ????????????????if (authProvider.Authenticate(model.UserName, model.Password))27 ????????????????{28 ????????????????????return Redirect(returnUrl ?? Url.Action("Index", "Admin"));29 ????????????????}30 ????????????????else31 ????????????????{32 ????????????????????ModelState.AddModelError("", "Incorrect username or password");33 ????????????????????return View();34 ????????????????}35 ????????????}36 ????????????else37 ????????????{38 ????????????????return View();39 ????????????}40 ????????}41 ????}42 }

这个控制器代码很简单。定义了两个Login的Action方法,一个用于接收Get请求,一个用于接收Post请求。

Post请求的Login方法,还接收了一个returnUrl字符串参数。他是过滤器拦截的页面URL。

调用authProvider.Authenticate返回表单验证结果。如果验证成功,则调用Redirect方法,将页面定向到刚才要访问的页面。

创建登录视图

 1 @model SportsStore.WebUI.Models.LoginViewModel 2 ?3 @{ 4 ????ViewBag.Title = "Admin: Login"; 5 ????Layout = "~/Views/Shared/_AdminLayout.cshtml"; 6 } 7 <div class="panel"> 8 ????<div class="panel-heading"> 9 ????????<h3>Log In</h3>10 ????????<p class="lead">11 ????????????Please log in to access the administration area:12 ????????</p>13 ????</div>14 ????<div class="panel-body">15 ????????@using (Html.BeginForm())16 ????????{17 ????????????<div class="panel-body">18 ????????????????@Html.ValidationSummary()19 ????????????????<div class="form-group">20 ????????????????????<label>User Name</label>21 ????????????????????@Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })22 ????????????????</div>23 ????????????????<div class="form-group">24 ????????????????????<label>Password</label>25  ???????????????????@Html.PasswordFor(m => m.Password, new { @class = "form-control" })26 ????????????????</div>27 ????????????????<input type="submit" value="Log in" class="btn btn-primary" />28 ????????????</div>29 ????????}30 ????</div>31 </div> ???????

它是一个简单的用户名密码登录视图。调用Html帮助方法PasswordFor生成密码输入框。

运行程序,当访问/Admin页面的时候,页面将自动跳转到/Account/Login页面,并在URL上添加?ReturnUrl=%2fAdmin后缀。

如果输入用户名user,密码123,点击Log in按钮后,跳转到Admin页面。

 自定义表单验证逻辑

上面的表单验证逻辑在非常简单的网站上是可以使用的。但是在真实的应用系统中,往往需要将用户名和密码记录在数据库表里,通过查询数据库验证用户名和密码是否正确。有时候,还需要在操作的时候,记录执行该操作的当前登录者。在首页上,有时候要显示当前登录者信息。下面将简单介绍这些功能怎样实现。

首先定义数据库实体类User。

1 namespace SportsStore.Domain.Entities2 {3 ????public class User4 ????{5 ????????public int UserID { get; set; }6 ????????public string UserName { get; set; }7 ????????public string Password { get; set; }8 ????}9 }

定义继承IIdentity接口的用户类UserIdentity。

 1 using SportsStore.Domain.Entities; 2 using System.Security.Principal; 3 ?4 namespace SportsStore.Infrastructure.Security 5 { 6 ????public class UserIdentity : IIdentity 7 ????{ 8 ????????public string AuthenticationType 9 ????????{10 ????????????get11 ????????????{12 ????????????????return "Form";13 ????????????}14 ????????}15 16 ????????public bool IsAuthenticated17 ????????{18 ????????????get;19 ????????????//extend property20 ????????????set;21 ????????}22 23 ????????public string Name24 ????????{25 ????????????get26 ????????????{27 ????????????????return User.UserName;28 ????????????}29 ????????}30 31 ????????public User User32 ????????{33 ????????????get;set;34 ????????}35 ????}36 }

接口IIdentity的定义如下:

继承的AutenticationType属性返回Form字符串。继承的IsAuthenticated属性,扩展了set访问器,增加了可写访问,让外部程序可以设置它的值。在实现类UserIdentity里增加了实体User类的属性。继承的Name属性返回User属性的属性Name。

定义继承IPrincipal接口的用户类UserProfile。

 1 using System.Security.Principal; 2 using System.Web; 3 ?4 namespace SportsStore.Infrastructure.Security 5 { 6 ????public class UserProfile : IPrincipal 7 ????{ 8 ????????public const string SessionKey = "User"; 9 10 ????????private UserIdentity _user;11 12 ????????public IIdentity Identity13 ????????{14 ????????????get15 ????????????{16 ????????????????return _user;17 ????????????}18 ????????????set //extended property19 ????????????{20 ????????????????_user = (UserIdentity)value;21 ????????????}22 ????????}23 24 ????????public bool IsInRole(string role)25 ????????{26 ????????????return true;27 ????????}28 29 ????????public static UserProfile CurrentLogonUser30 ????????{31 ????????????get32 ????????????{33 ????????????????if (HttpContext.Current.Session == null)34 ????????????????{35 ????????????????????return null;36 ????????????????}37 ????????????????if (HttpContext.Current.Session[SessionKey] == null)38 ????????????????{39 ????????????????????return null;40 ????????????????}41 ????????????????return HttpContext.Current.Session[SessionKey] as UserProfile;42 ????????????}43 ????????}44 ????}45 }

接口IPrincipal的定义如下:

继承类UserProfile,定义了一个私有的UserIdentity类型的_user字段,通过继承的属性Identity返回它的值。继承的属性Identity扩展了它的可写访问器,让外部程序可以设置它的值。继承的方法IsInRole暂时返回true。

在UserProfile类里还定义了一个UserProfile类型的静态属性CurrentLogonUser,他用于在应用程序的任何地方返回当前登录用户的信息。从它的代码看到,我将使用Session存储当前登录用户对象。

修改接口IAuthProvider和类FormsAuthProvider。

 1 using SportsStore.Infrastructure.Security; 2 using SportsStore.WebUI.Models; 3 ?4 namespace SportsStore.Infrastructure.Abstract 5 { 6 ????public interface IAuthProvider 7 ????{ 8 ????????bool Authenticate(LoginViewModel loginModel, out UserProfile userProfile); 9 ????}10 }

方法Authenticate增加了一个out修饰的UserProfile参数,用于返回验证成功后的UserProfile类型对象。

using SportsStore.Infrastructure.Abstract;using SportsStore.Infrastructure.Security;using SportsStore.WebUI.Models;namespace SportsStore.WebUI.Infrastructure.Concrete{ ???public class FormsAuthProvider : IAuthProvider ???{ ???????public bool Authenticate(LoginViewModel loginModel, out UserProfile userProfile) ???????{ ???????????//validate user and password from database ???????????bool result = true; ???????????userProfile = new UserProfile(); ???????????if (result) ???????????{ ???????????????UserIdentity userIdentity = new UserIdentity(); ???????????????userIdentity.IsAuthenticated = true; ???????????????// get user entity from db ???????????????userIdentity.User = new Domain.Entities.User() ???????????????{ ???????????????????UserID = 0, ???????????????????UserName = loginModel.UserName, ???????????????????Password = loginModel.Password ???????????????}; ???????????????userProfile.Identity = userIdentity; ???????????} ???????????return result; ???????} ???}}

方法Authenticate将查询数据库验证用户名和密码,如果验证通过,将数据库读出来的用户信息生成UserProfile对象,用out参数返回。

你可以使用Ninject注册一个IUserRepository到类FormsAuthProvider,再读数据库。

IUserRepository接口:

 1 using SportsStore.Domain.Entities; 2 using System.Collections.Generic; 3 ?4 namespace SportsStore.Domain.Abstract 5 { 6 ????public interface IUserRepository 7 ????{ 8 ????????IEnumerable<User> Users { get; } 9 ????}10 }

实现类EFUserRepository:

 1 using SportsStore.Domain.Abstract; 2 using SportsStore.Domain.Entities; 3 using System.Collections.Generic; 4 ?5 namespace SportsStore.Domain.Concrete 6 { 7 ????public class EFUserRepository : IUserRepository 8 ????{ 9 ????????private EFDbContext context = new EFDbContext();10 ????????public IEnumerable<User> Users11 ????????{12 ????????????get13 ????????????{14 ????????????????try15 ????????????????{16 ????????????????????return context.Users;17 ????????????????}18 ????????????????catch (System.Exception e)19 ????????????????{20 ????????????????????return null;21 ????????????????}22 ????????????}23 ????????}24 ????}25 }

EFDbContext:

 1 using SportsStore.Domain.Entities; 2 using System.Data.Entity; 3 ?4 namespace SportsStore.Domain.Concrete 5 { 6 ????public class EFDbContext : DbContext 7 ????{ 8 ????????public DbSet<Product> Products { get; set; } 9 10 ????????public DbSet<User> Users { get; set; }11 ????}12 }

修改后的FormsAuthProvider:

 1 using SportsStore.Domain.Abstract; 2 using SportsStore.Infrastructure.Abstract; 3 using SportsStore.Infrastructure.Security; 4 using SportsStore.WebUI.Models; 5 using System.Linq; 6 ?7 namespace SportsStore.WebUI.Infrastructure.Concrete 8 { 9 ????public class FormsAuthProvider : IAuthProvider10 ????{11 ????????private IUserRepository _userRepository;12 13 ????????public FormsAuthProvider(IUserRepository userRepository)14 ????????{15 ????????????_userRepository = userRepository;16 ????????}17 18 ????????public bool Authenticate(LoginViewModel loginModel, out UserProfile userProfile)19 ????????{20 ????????????//validate user and password from database21 ????????????var user = _userRepository.Users.Where(u => u.UserName == loginModel.UserName && u.Password == loginModel.Password).FirstOrDefault();22 ????????????bool result = user != null;23 ????????????userProfile = new UserProfile();24 ????????????if (result)25 ????????????{26 ????????????????UserIdentity userIdentity = new UserIdentity();27 ????????????????userIdentity.IsAuthenticated = true;28 ????????????????// get user entity from db29 ????????????????userIdentity.User = user;30 31 ????????????????userProfile.Identity = userIdentity;32 ????????????}33 ????????????return result;34 ????????}35 ????}36 }

类NinjectDependencyResolver里的AddBindings方法添加绑定:

1 kernel.Bind<IUserRepository>().To<EFUserRepository>();

还需要添加用户表Users。

里面添加一行数据。

添加继承自AuthorizeAttribute类的自定义Authorize特性类MyAuthorizeAttribute。

 1 using System.Web; 2 using System.Web.Mvc; 3 ?4 namespace SportsStore.Infrastructure.Security 5 { 6 ????public class MyAuthorizeAttribute : AuthorizeAttribute 7 ????{ 8 ????????protected override bool AuthorizeCore(HttpContextBase httpContext) 9 ????????{10 ????????????UserProfile userProfile = null;11 ????????????if (httpContext.Session != null)12 ????????????{13 ????????????????userProfile = (UserProfile)httpContext.Session[UserProfile.SessionKey];14 ????????????}15 ????????????if (userProfile == null)16 ????????????{17 ????????????????return false;18 ????????????}19 ????????????else20 ????????????{21 ????????????????//some other validate logic here, like intercept IP22 ????????????????return userProfile.Identity.IsAuthenticated;23 ????????????}24 ????????}25 ????}26 }

该类是根据Session对象存储的User对象。拦截控制器方法。

将自定义AuthorizeAttribute特性MyAuthorizeAttribute应用到AdminController控制器。

1  ???[MyAuthorize] 2 ????public class AdminController : Controller3 ????{

最后是修改Account控制器的Login方法。

 1 ????????[HttpPost] 2 ????????public ActionResult Login(LoginViewModel model, string returnUrl) 3 ????????{ 4 ????????????if (ModelState.IsValid) 5 ????????????{ 6 ????????????????UserProfile userProfile; 7 ????????????????if (authProvider.Authenticate(model, out userProfile)) 8 ????????????????{ 9 ????????????????????HttpContext.Session[UserProfile.SessionKey] = userProfile;10 ????????????????????return Redirect(returnUrl ?? Url.Action("Index", "Admin"));11 ????????????????}12 ????????????????else13 ????????????????{14 ????????????????????ModelState.AddModelError("", "Incorrect username or password");15 ????????????????????return View();16 ????????????????}17 ????????????}18 ????????????else19 ????????????{20 ????????????????return View();21 ????????????}22 ????????}

调用的authProvider.Authenticate方法增加了out参数userProfile。如果userProfile对象写入Session。

在首页上显示当前登录用户

在AdminController控制器里,添加LogonUser方法Action,返回包含当前登录用户对象的PartialViewResult。

1 ????????public PartialViewResult LogonUser()2 ????????{3 ????????????var user = UserProfile.CurrentLogonUser != null ? UserProfile.CurrentLogonUser.Identity as UserIdentity : null;4 ????????????if (user != null)5 ????????????{6 ????????????????return PartialView("LogonUser", user.User);7 ????????????}8 ????????????return PartialView();9 ????????}

在_AdminLayout.cshtml视图中嵌入这个Action。

 1 <!DOCTYPE html> 2 ?3 <html> 4 <head> 5 ????<meta name="viewport" content="width=device-width" /> 6 ????<link href="~/Content/bootstrap.css" rel="stylesheet" /> 7 ????<link href="~/Content/bootstrap-theme.css" rel="stylesheet" /> 8 ????<link href="~/Content/ErrorStyles.css" rel="stylesheet" /> 9 ????<script src="~/Scripts/jquery-1.9.1.js"></script>10 ????<script src="~/Scripts/jquery.validate.js"></script>11 ????<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>12 ????<title>@ViewBag.Title</title>13 </head>14 <body>15 ????<div class="navbar navbar-inverse" role="navigation">16 ????????<a class="navbar-brand" href="#">17 ????????????<span class="hidden-xs">SPORTS STORE</span>18 ????????????<div class="visible-xs">SPORTS</div>19 ????????????<div class="visible-xs">STORE</div>20 ????????</a>21 ????????<span class="hidden-xs hidden-sm">22  ???????????@Html.Action("LogonUser", "Admin")23 ????????</span>24 ????</div>25 ????<div>26 ????????@if (TempData["message"] != null)27 ????????{28 ????????????<div class="alert alert-success">@TempData["message"]</div>29 ????????}30 ????????@RenderBody()31 ????</div>32 </body>33 </html>

运行程序,程序运行结果跟之前一样。登录成功后,在首页的右上角显示当前登录用户的用户名。

跟我学ASP.NET MVC之十:SportsStrore安全

原文地址:https://www.cnblogs.com/uncle_danny/p/9085035.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved