آموزش Authentication در MVC
بسم الله الرحمن الرحیم
آموزش Asp.net Identity MVC (بخش اول)
آموزش Asp.net Identity (بخش دوم)
آموزش Asp.net Membership (رفع خطا)
در بعضی مواقع ممکن است شما از نوع پیش فرض authentication نمی خواهید استفاده می کنید و دوست دارید یک احراز هویت اختصاصی برای خود داشته باشید. هر کاربر شما یک کاربر منحصر به فرد خواهد بود که مجموعه ای از سطوح دسترسی را برای دسترسی به بخش های مختلف وب سایت شما خواهد داشت. وقتی که کاربر شما تایید شود می تواند وابسته به Role هایی که دارد از منابع مختلف استفاده کند.
ASP.NET به وسیله دو interface به نام های، IPrincipal and IIdentity سطوح دسترسی و identity و Role را برای هر کاربر ارائه می دهد. شما می توانید یک سطح دسترسی اختصاصی برای خود به وسیله اینترفیس های IPrincipal and IIdentity، که در HttpContext باند شده اند ایجاد نمایید.
public class CustomPrincipal : IPrincipal { public IIdentity Identity { get; private set; } public bool IsInRole(string role) { if (roles.Any(r => role.Contains(r))) { return true; } else { return false; } } public CustomPrincipal(string Username) { this.Identity = new GenericIdentity(Username); } public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string[] roles { get; set; } }
بسم الله الرحمن الرحیم
آموزش Asp.net Identity MVC (بخش اول)
آموزش Asp.net Identity (بخش دوم)
آموزش Asp.net Membership (رفع خطا)
در بعضی مواقع ممکن است شما از نوع پیش فرض authentication نمی خواهید استفاده می کنید و دوست دارید یک احراز هویت اختصاصی برای خود داشته باشید. هر کاربر شما یک کاربر منحصر به فرد خواهد بود که مجموعه ای از سطوح دسترسی را برای دسترسی به بخش های مختلف وب سایت شما خواهد داشت. وقتی که کاربر شما تایید شود می تواند وابسته به Role هایی که دارد از منابع مختلف استفاده کند.
ASP.NET به وسیله دو interface به نام های، IPrincipal and IIdentity سطوح دسترسی و identity و Role را برای هر کاربر ارائه می دهد. شما می توانید یک سطح دسترسی اختصاصی برای خود به وسیله اینترفیس های IPrincipal and IIdentity، که در HttpContext باند شده اند ایجاد نمایید.
public class CustomPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role)
{
if (roles.Any(r => role.Contains(r)))
{
return true;
}
else
{
return false;
}
}
public CustomPrincipal(string Username)
{
this.Identity = new GenericIdentity(Username);
}
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string[] roles { get; set; }
}
ASP.NET Forms Authentication
ASP.NET forms authentication پس از اینکه IIS authentication کامل شد اجرا می شود. شما می توانید forms authentication را در webconfig تنظیم کنید. مقدار forms authentication به صورت پیش فرض به صورت زیر است:
<system.web>
<authentication mode="Forms">
<forms loginUrl="Login.aspx"
protection="All"
timeout="30"
name=".ASPXAUTH"
path="/"
requireSSL="false"
slidingExpiration="true"
defaultUrl="default.aspx"
cookieless="UseDeviceProfile"
enableCrossAppRedirects="false" />
</authentication>
</system.web>
کلاس FormsAuthentication به صورت اتوماتیک پس از فراخوانی SetAuthCookie() یا متد RedirectFromLoginPage() کوکی مربوط به احراز هویت را ایجاد می کند.
مقداری که در authentication cookie ذخیره می شود یک رشته است که مقدار امضا و encrypt شده شی FormsAuthenticationTicket است.
شما می توانید شی FormsAuthenticationTicket را به وسیله مشخص کردن مقادیر زیر ایجاد کنید
نام کوکی
نسخه یا همان ورژن کوکی
مسیر یا directory path
تاریخ صدور یا ایجاد کوکی
تاریخ انقضا کوکی
چطور کوکی باید ادامه داشته باشد
و در نهایت اطلاعات اضافی که توسط شما می تواند اضافه تعریف می شود
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
"userName",
DateTime.Now,
DateTime.Now.AddMinutes(30), // value of time out property
false, // Value of IsPersistent property
String.Empty,
FormsAuthentication.FormsCookiePath);
حالا می توانید ticket به وسیله متد Encrypt از کلاس FormsAuthentication، encrypt کنید.
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
در FormsAuthenticationTicket encrypt، تیکت خاصیت protection از عناصر فرم ها را به مقدارAll یا Encryption مقداردهی می کند.
Custom Authorization
ASP.NET MVC به وسیله فیلتر Authorizationبررسی هویت کاربر یا همان authorize را انجام می دهد. این فیلتر می تواند یک action، Controller و یا به صورت کلی برنامه را بررسی و تایید کند. این فیلتر در کلاس AuthorizeAttribute قرار دارد. شما می توانید این فیلتر را بر اساس نیاز خود شخصی سازی (Customize) کنید برای این کار باید متد OnAuthorization() را بازنویسی کنید:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string UsersConfigKey { get; set; }
public string RolesConfigKey { get; set; }
protected virtual CustomPrincipal CurrentUser
{
get { return HttpContext.Current.User as CustomPrincipal; }
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
var authorizedUsers = ConfigurationManager.AppSettings[UsersConfigKey];
var authorizedRoles = ConfigurationManager.AppSettings[RolesConfigKey];
Users = String.IsNullOrEmpty(Users) ? authorizedUsers : Users;
Roles = String.IsNullOrEmpty(Roles) ? authorizedRoles : Roles;
if (!String.IsNullOrEmpty(Roles))
{
if (!CurrentUser.IsInRole(Roles))
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
// base.OnAuthorization(filterContext); //returns to login url
}
}
if (!String.IsNullOrEmpty(Users))
{
if (!Users.Contains(CurrentUser.UserId.ToString()))
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
// base.OnAuthorization(filterContext); //returns to login url
}
}
}
}
}
User Authentication
یک کاربر زمانی احراز هویتش تایید شده است که پراپرتی IsAuthenticated مقدار true برگرداند.
برای احراز هویت یک کاربر شما می توانید به دو روش این کار را انجام دهید:
1. Thread.CurrentPrincipal.Identity.IsAuthenticated
2. HttpContext.Current.User.Identity.IsAuthenticated
Designing Data Model
حالا وقت آن است که ما کلاس های data access model را برای ایجاد کاربران و Role های آن ایجاد کنیم:
public class User
{
public int UserId { get; set; }
[Required]
public String Username { get; set; }
[Required]
public String Email { get; set; }
[Required]
public String Password { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public Boolean IsActive { get; set; }
public DateTime CreateDate { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
[Required]
public string RoleName { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
تعریف Database Context با code first mapping میان کاربران و سطوح دسترسی
با استفاده از روش Entity Framework code first، یک DataContext با entity هایUser و Role و روابط بین آنها ایجاد می کنیم.
public class DataContext : DbContext
{
public DataContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany(r=>r.Users)
.Map(m =>
{
m.ToTable("UserRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
Code First Database Migrations
به کمک entity framework code first database migrations یک دیتابیس با نام security در SQL Server ایجاد می کنیم.
دستورات زیر را در Visual Studio Package Manager Console یکی پس از دیگری ایجاد کنید تا کد شما با دیتابیس migrate شود
بعد از اجرای دستور اول، enabling migrations ، در فولدر Migrations،در فایل Configuration.cs مقدارseed اضافه می شود
هنگامی که سه خط دستور بالا را اجرا می کنید در صورتی که با موفقیت این دستورات اجرا شوند، باید فایل ها وفولدرهای زیر به ساختار پروژه شما اضافه شود، همچنین دیتابیس شما نیز در SQL Server ایجاد شود:
protected override void Seed(Security.DAL.DataContext context)
{
Role role1 = new Role { RoleName = "Admin" };
Role role2 = new Role { RoleName = "User" };
User user1 = new User { Username = "admin", Email = "admin@ymail.com", FirstName = "Admin", Password = "123456", IsActive = true, CreateDate = DateTime.UtcNow, Roles = new List() };
User user2 = new User { Username = "user1", Email = "user1@ymail.com", FirstName = "User1", Password = "123456", IsActive = true, CreateDate = DateTime.UtcNow, Roles = new List() };
user1.Roles.Add(role1);
user2.Roles.Add(role2);
context.Users.Add(user1);
context.Users.Add(user2);
}
Designing View Model
یک ViewModel کلاس برای مدیریت فرایند لاگین به صورت زیر ایجاد کنید:
public class LoginViewModel
{
[Required]
[Display(Name = "User name")]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public class CustomPrincipalSerializeModel
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string[] roles { get; set; }
}
Forms Authentication Initialization
public class AccountController : Controller
{
DataContext Context = new DataContext();
//
// GET: /Account/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(LoginViewModel model, string returnUrl = "")
{
if (ModelState.IsValid)
{
var user = Context.Users.Where(u => u.Username == model.Username && u.Password == model.Password).FirstOrDefault();
if (user != null)
{
var roles=user.Roles.Select(m => m.RoleName).ToArray();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.UserId = user.UserId;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
serializeModel.roles = roles;
string userData = JsonConvert.SerializeObject(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
user.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false, //pass here true, if you want to implement remember me functionality
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
if(roles.Contains("Admin"))
{
return RedirectToAction("Index", "Admin");
}
else if (roles.Contains("User"))
{
return RedirectToAction("Index", "User");
}
else
{
return RedirectToAction("Index", "Home");
}
}
ModelState.AddModelError("", "Incorrect username and/or password");
}
return View(model);
}
[AllowAnonymous]
public ActionResult LogOut()
{
FormsAuthentication.SignOut();
return RedirectToAction("Login", "Account", null);
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Database.SetInitializer<DataContext>(new DataContextInitilizer());
}
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
CustomPrincipalSerializeModel serializeModel = JsonConvert.DeserializeObject<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.UserId = serializeModel.UserId;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
newUser.roles = serializeModel.roles;
HttpContext.Current.User = newUser;
}
}
}
Base Controller for accessing Current User
برای دسترسی به داده های کاربران در همه کنترلر ها باید یکBase Controller ایجاد کنید. تمامی کنترلر های شما برای دسترسی به اطلاعات کاربران از UserContext باید از این کنترلر ارث بری کنند.
public class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
public class HomeController : BaseController
{
//
// GET: /Home/
public ActionResult Index()
{
string FullName = User.FirstName + " " + User.LastName;
return View();
}
}
Base View Page برای دسترسی به کاربر جاری
یک Base Class برای دسترسی به اطلاعات کاربر جاری در تمامی Viewهای شما باید به صورت زیر ایجاد شود:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
این کلاس در \Views\Web.config به عنوان Base Class ثبت کنید
<system.web.webPages.razor>
<!--Other code has been removed for clarity-->
<pages pageBaseType="Security.DAL.Security.BaseViewPage">
<namespaces>
<!--Other code has been removed for clarity-->
</namespaces>
</pages>
</system.web.webPages.razor>
حالا شما می توانید به سادگی مانند زیر به اطلاعات کاربر لاگین کرده دسترسی پیدا کنید
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h4>Welcome : @User.FirstName</h4>
<h1>Admin DashBoard</h1>
Login View
@model Security.Models.LoginViewModel
@{
ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User Login</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.Username, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Username)
@Html.ValidationMessageFor(model => model.Username)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Password)
@Html.ValidationMessageFor(model => model.Password)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RememberMe, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.RememberMe)
@Html.ValidationMessageFor(model => model.RememberMe)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Login" class="btn btn-default" />
</div>
</div>
</div>
}
CustomAuthorize
برای امن کردن صفحات Admin و user می توانید به کنترلرهای Admin وUser خاصیت CustomAuthorize را که در بالا تعریف شده است اضافه کنید:
[CustomAuthorize(Roles= "Admin")]
// [CustomAuthorize(Users = "1")]
public class AdminController : BaseController
{
//
// GET: /Admin/
public ActionResult Index()
{
return View();
}
}
[CustomAuthorize(Roles= "User")]
// [CustomAuthorize(Users = "1,2")]
public class UserController : BaseController
{
//
// GET: /User/
public ActionResult Index()
{
return View();
}
}
همچنین شما می توانید برای کنترلرها و سطوح دسترسی آنها برای کاربران مختلف در فایل web.config به صورت Hard Code مقادیر را برای دسترسی ها و کاربران مختلف ذخیره کنید:
<add key="RolesConfigKey" value="Admin"/>
<add key="UsersConfigKey" value="2,3"/>
می توانید این کلیدها را با استفاده از خاصیت CustomAuthorize به صورت زیر استفاده کنید:
//[CustomAuthorize(RolesConfigKey = "RolesConfigKey")]
[CustomAuthorize(UsersConfigKey = "UsersConfigKey")]
public class AdminController : BaseController
{
//
// GET: /Admin/
public ActionResult Index()
{
return View();
}
}
[CustomAuthorize(RolesConfigKey = "RolesConfigKey")]
// [CustomAuthorize(UsersConfigKey = "UsersConfigKey")]
public class UserController : BaseController
{
//
// GET: /User/
public ActionResult Index()
{
return View();
}
}
Test
هنگامی که برنامه را اجرا می کنید شما باید در صفحه لاگین، اطلاعات خود را وارد کنید (از نام کاربری و کلمه عبور user1 استفاده کنید) تا وارد بخش مدیریت user ها شوید.
هنگامی که کاربر تلاش کند به صفحات unauthorized مانند صفحات Admin دسترسی پیدا کند با خطای زیر مواجه می شود
بنیامین
سلام من عمل کردم ولی این error v رو میده لظفا کمکم کنید>>
اسماعیلشیدایی
با عرض سلام
حمید
ali
با تشکر از سایت خوبتون
فرشید
سلام
اسماعیلشیدایی
با عرض سلام
ممنون از لطف شما
hadi
سلام وقتتون بخیر. کارتون خوبه. ولی کد هارو تیکه تیکه گذاشتین و مشخص نکردین کد ها برای کدوم کلاس و این آدمو گیج میکنه
اسماعیلشیدایی
با عرض سلام
ممنون از پیشنهاد شما، انشالله سعی می کنیم هم در نوشتن مطالب بیشتر دقت کنیم و هم کل پروژه را برای دانلود بر روی سایت قرار دهیم.
ستاره
سلام دستتون درد نکنه خیلی خوب بود
یک مبحثی هم هست به اسم RoleProvider که کار Authentication را انجام میده میشه نظرتون راجح به اون عنوان کنید؟ به نظر شما استفاده از کدوم بهتره؟
اسماعیلشیدایی
یا عرض سلام
RoleProvider برای ساخت Role های اختصاصی و احراز هویت کاربران معرفی شدند اما با معرفی شدن ASP.Net identity مباحث کاملتر شدند و عملکرد ها به شکلی تغییر یافت که کارهای مربوط به تعیین سطوح دسترسی به دلیل cache شدن اطلاعات در کوکی به جای Session فشار از روی سرور به روی کلاینتها انتقال یافت سرعت و اطلاعات، کاهش هزینه ارتقا سرور ها و... بسیار بهینه شد. RoleProvider اساسا زیر مجموعه membership می باشد و برای بهره گیری از این روش معرفی شده است.
طبیعتا نکته ای که وجود دارد، باید برای استفاده از هر ابزار، کاربرد و هزینه اتفاده از ان را در نظر گرفت و نمی توان به صورت کلی ابزاری را سودمند و ابزاری را نامناسب خواند.
یاعلی
محسن
سلام
مینا
سلام
اسماعیلشیدایی
با عرض سلام
مقالات زیادی در خصوص سطوح دسترسی نوشته شده است. می توانید از این بخش این موارد را مطالعه بفرمایید:
موفق باشید
علیرضا
سلام وقت بخیر
اسماعیلشیدایی
با عرض سلام
بر روی چشم، حتما، البته بسیاری از آموزش ها دارای کد پایانی نیز هستند.
یاعلی
حسین
سلام دوست عزیز
مطالبتون خیلی روون بود.
سوالی که برای من پیش آمده اینه که آیا ASP.NET Identity هم در پس زمینه از Forms Authentication داره استفاده میکنه؟
اگر پاسخ مثبت است پس لزوما احتیاجی به استفاده از Identity نیست؟ البته باید در نظر داشت که در کدهای احراز هویت دقت شود؟درسته؟
ممنون
اسماعیلشیدایی
با عرض سلام
خواهش می کنم.
خیر اینطور نیست ASP.NET Identity از Owin استفاده می کند که بر اساس معماری کوکی پیاده سازی شده است، لطفا مقاله ابتدایی این دوره را مطالعه کنید تا انشالله جواب کاملی بدست آورید.
موفق باشید
vahid
دوست عزیز بخاطر مطالب مفیدتون بسیار سپاسگزام امیدوارم پاسخ این نشر علم را در زندگیتان حس کنید.
موفق باشید
اسماعیلشیدایی
ممنون
انشالله همه به آنچه صلاحمان است دست یابیم
یاعلی
بهزاد شیرانی
واقعا عالی بود
اسماعیلشیدایی
لطف دارید
انشالله که همه مطالب همینطور مناسب و کارایی داشته باشند.
www.cwx121.com
www.cwx121.com
mostafa
ممنونم از اموزش خوبتون فقط اگر امکانش هست با database first هم توضیح بدید چون واقعا از codefirst سردرنمیارم و علاقه ای هم ندارم تشکر
مدیروب سایت
سلام دوست عزیز
در اسرع وقت آموزش این قسمت را خواهیم گذاشت.
موفق باشید