摘要:
在之前的文章中,我给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