ایجاد سایت امن در ASP.NET MVC
ایجاد سایت امن در ASP.NET MVC

ایجاد سایت امن در ASP.NET MVC

امروز در این آموزش قصد داریم تا به شما نحوه ایجاد یک وبسایت امن رو در  ASP.NET MVC بدهیم.آنچه که شما در این آموزش یاد خواهید گرفت عبارتند از :

ساخت یک پروژه در Asp.net mvc ،تایید ایمیل،بررسی ایمیل تایید شده قبل از ورود کاربر به سایت،بازیابی رمز عبور،ارسال دوباره ایمیل فعال سازی،مرا به یاد داشته باش،تاریخ انقضا برای لینک فعال سازی،استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت در مقابل حملات،جلوگیری از حملات Brute-forceو…

برای مشاهده لینک وارد شود

ساخت یک پروژه در Asp.net mvc

ابتدا ویژوال استدیو ۲۰۱۳ را اجرا می‌کنیم.

۱.برای شروع یک پروژه با asp.net mvc ایجاد می‌کنیم

ساخت یک پروژه در Asp.net mvc

ساخت یک پروژه در Asp.net mvc
ساخت یک پروژه در Asp.net mvc-2

ساخت یک پروژه در Asp.net mvc-2
ساخت یک پروژه در Asp.net mvc-3

ساخت یک پروژه در Asp.net mvc-3

۲ . پروژه را اجرا کنید. با اجرا برای اولین، یک پایگاه داده در مسیر App_Data ایجاد خواهد شد. پایگاه داده را کشیده (Drag) و در Solution Explorer در مسیر App_Data رها کنید (Drop). حال به پروژه اجرا شده بروید و روی Register کلیک کنید و ثبت نام کنید. در این مرحله، تایید(validation) توسط یک ایمیل صورت می‌گیرد

۳ . به Server Explorer رفته و روی پایگاه داده کلیک کنید. در Tables جدولی با نام AspNetUsers وجود دارد که اطلاعات کاربران را در خود نگه می دارد.

شمای جدول AspNetUsers به شکل زیر می باشد:

ساخت یک پروژه در Asp.net mvc-4

ساخت یک پروژه در Asp.net mvc-4

۴ . روی جدول AspNetUser کلیک راست کنید و Show Table Data را بزنید.

ساخت یک پروژه در Asp.net mvc-5

ساخت یک پروژه در Asp.net mvc-5

در این مرحله مشاهده می‌کنید که ایمیل هنوز تایید نشده است. این فیلد را حذف کنید. در مرحله بعد ایمیل وارد شده شما بایستی تایید شود.

تایید ایمیل

به مسیر App_Start/IdentityConfig.cs بروید. کلاسی با نام EmailService وجود دارد که شامل تابع SendAsync می باشد. وظیفه این تابع ارسال ایمیل می باشد ولی مشاهده می‌کنید که هیچ کدی برای این کار داخل تابع وجود ندارد. برای ارسال ایمیل بایستی کدهای مربوطه را وارد نماییم. ابتدا نیاز داریم یک ایمیل ایجاد کنیم و اطلاعات آن را نیز داشته باشیم. اطلاعاتی که نیاز داریم آدرس هاست، پورت مورد استفاده، نام کاربری ایمیل و رمز عبور آن است. این اطلاعات را در فایل کانفیگ(web.Config) در مسیر appSettings وارد نمایید.

</connectionStrings>
  <appSettings>
    <!-- Email service account connection information -->
    <add key="mailHost" value=" Host Address " />
    <add key="mailPort" value=" Host Port " />
    <add key="mailAccount" value=" Email Account " />
    <add key="mailPassword" value=" Email Password " />
  </appSettings>
  <system.web>

برای کدهای ارسال ایمیل دو امکان وجود دارد.

  1. استفاده از امکان خود .Net (SmtpClient)

یک تابع با نام ConfigSendGridasync در زیر تابع SendAsync ایجاد کنید و کد مربوط به ارسال ایمیل را وارد نمایید:

public class EmailService : IIdentityMessageService
    {
        public async Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            await ConfigSendGridasync(message);
        }

        private async Task ConfigSendGridasync(IdentityMessage message)
        {
            using (var client = new SmtpClient())
            {
                client.Port =Int32.Parse(ConfigurationManager.AppSettings["mailPort"]);
                client.Host = ConfigurationManager.AppSettings["mailHost"];
                client.EnableSsl = false;
                client.Timeout = 10000;
                client.DeliveryMethod = SmtpDeliveryMethod.Network;
                client.UseDefaultCredentials = false;
                client.Credentials = new System.Net.NetworkCredential(
                    ConfigurationManager.AppSettings["mailAccount"],
                    ConfigurationManager.AppSettings["mailPassword"]);


                string text = message.Subject;
                string html = message.Body;

                var msg = new MailMessage();
                msg.From = new MailAddress("info@8np.ir");
                msg.To.Add(new MailAddress(message.Destination));
                msg.Subject = message.Subject;
                msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
                msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

                client.Send(msg);
            }
        }
    }

۲ . استفاده از پکیج (SendGrid nuget)

برای استفاده از SendGrid باید پکیج آن را نصب کنید. در Package Console Manager دستور زیر را وارد کنید:

Install-Package SendGrid

به صفحه ورود Azure SendGrid  رفته و یک به صورت رایگان ثبت نام کنید. کد زیر را برای SendGrid وارد نمایید.

public class EmailService : IIdentityMessageService
    {
        public async Task SendAsync(IdentityMessage message)
        {
            await configSendGridasync(message);
        }

        // Use NuGet to install SendGrid (Basic C# client lib) 
        private async Task configSendGridasync(IdentityMessage message)
        {
            var myMessage = new SendGridMessage();
            myMessage.AddTo(message.Destination);
            myMessage.From = new System.Net.Mail.MailAddress(
                                "info@8np.ir", "Reza");
            myMessage.Subject = message.Subject;
            myMessage.Text = message.Body;
            myMessage.Html = message.Body;

            var credentials = new NetworkCredential(
                       ConfigurationManager.AppSettings["mailAccount"],
                       ConfigurationManager.AppSettings["mailPassword"]
                       );

            // Create a Web transport for sending email.
            var transportWeb = new Web(credentials);

            // Send the email.
            if (transportWeb != null)
            {
                await transportWeb.DeliverAsync(myMessage);
            }
            else
            {
                Trace.TraceError("Failed to create Web transport.");
                await Task.FromResult(0);
            }
        }
    }

شما به Namespace زیر نیاز دارید:

using SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;

در وب کانفیگ اطلاعات زیر را وارد کنید:

</connectionStrings>
   <appSettings>
      <add key="webpages:Version" value="3.0.0.0" />
      <!-- Markup removed for clarity. -->
      
      <add key="mailAccount" value="xyz" />
      <add key="mailPassword" value="password" />
   </appSettings>
  <system.web>

نکته امنیتی: به یاد داشته باشید که به هیچ وجه اطلاعات حساس خود را در داخل سورس کد استفاده نکنید. آن‌ها را در appSetting نگه داری کنید.

حال بایستی برای ارسال ایمیل فعال سازی تغییراتی را در Account controller ایجاد کنیم:

تابع SendEmailConfirmationTokenAsync را در انتهای کنترلر در ناحیه Helpers وارد نمایید:

#region Helpers 
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
    string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
    var callbackUrl = Url.Action("ConfirmEmail", "Account",
        new { userId = userID, code = code }, protocol: Request.Url.Scheme);
    await UserManager.SendEmailAsync(userID, subject,
        "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");

    return callbackUrl;
}
#endregion

سپس تایع Register را به شکل زیر تغییر دهید:

//

// POST: /Account/Register

[HttpPost]

[AllowAnonymous]

[ValidateAntiForgeryToken]

[CaptchaValidator]

public async Task<ActionResult> Register(RegisterViewModel model)

{

    if (ModelState.IsValid)

    {

        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };

        IdentityResult result = await UserManager.CreateAsync(user, model.Password);

        if (result.Succeeded)

        {

            //  Comment the following line to prevent log in until the user is confirmed.

            //  await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);

 

            string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");

 

            // Uncomment to debug locally 

            // TempData["ViewBagLink"] = callbackUrl;

            ViewBag.Message = "Check your email and confirm your account, you must be confirmed "

                            + "before you can log in.";

 

            return View("Info");

            //return RedirectToAction("Index", "Home");

        }

        AddErrors(result);

    }

 

    // If we got this far, something failed, redisplay form

    return View(model);

}

در مسیر Views/Shared یک PatialView به نام Info ایجاد کنید و کد زیر را در آن قرار دهید:

@{
    ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

حال وقتی ثبت نام کنید یک لینک فعال سازی به ایمیل کاربری شما ارسال می شود و پیغام زیر را مشاهده می نماییم:

تایید ایمیل

تایید ایمیل

وارد ایمیل خود شده و روی لینک فعال سازی کلیک کنید.

بررسی ایمیل تایید شده قبل از ورود کاربر به سایت (log in)

حال بعد از اینکه کاریر عملیات ثبت نام را با موفقیت به پایان رساند، برای فعال شدن حساب کاربری و تایید ایمیل، باید بر روی لینک فعال سازی کلیک کند. اگر این کار را نکند اجازه ورود به حساب کاربری را نخواهد داشت. پس باید تغییراتی را در تابع Login ایجاد نماییم:

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    // Require the user to have a confirmed email before they can log on.
    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            ViewBag.errorMessage = "You must have a confirmed email to log on. ";
            return View("Error");
        }
    }

    // This doesn't count login failures towards account lockout
    // To enable password failures to trigger account lockout, change to shouldLockout: true
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

حال نیاز داریم در صورت تایید نشدن ایمیل پیغام خطایی به کاربر نشان دهیم که در ViewBag.errorMessage ذخیره شده است. به مسیر Views/Shared/Error.cshtml رفته و آن را به شکل زیر تغییر دهید

@model System.Web.Mvc.HandleErrorInfo

@{
    ViewBag.Title = "Error";
}

<h1 class="text-danger">Error.</h1>
@{
    if (String.IsNullOrEmpty(ViewBag.errorMessage))
    {
        <h2 class="text-danger">An error occurred while processing your request.</h2>
    }
    else
    {
        <h2 class="text-danger">@ViewBag.errorMessage</h2>
    }
}

بازیابی رمز عبور

در Account controller به تابع ForgotPassword رفته و به شکل زیر تغییر دهید:

// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByNameAsync(model.Email);
        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            // Don't reveal that the user does not exist or is not confirmed
            return View("ForgotPasswordConfirmation");
        }

        // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
        // Send an email with this link
        string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
        var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);		
        await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");
        return RedirectToAction("ForgotPasswordConfirmation", "Account");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

به مسیر Views\Account\Login.cshtml مراجعه کنید و به شکل زیر تغییر دهید:

@using WebTemplate_02.Models
@model LoginViewModel
@{
    ViewBag.Title = "Log in";
}

<h2>@ViewBag.Title.</h2>
<div class="row">
    <div class="col-md-8">
        <section id="loginForm">
            @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4>Use a local account to log in.</h4>
                <hr />
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                <div class="form-group">
                    @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.RememberMe)
                            @Html.LabelFor(m => m.RememberMe)
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Log in" class="btn btn-default" />
                    </div>
                </div>
                <p>
                    @Html.ActionLink("Register as a new user", "Register")
                </p>
                @* Enable this once you have account confirmation enabled for password reset functionality*@
                    <p>
                        @Html.ActionLink("Forgot your password?", "ForgotPassword")
                    </p>
            }
        </section>
    </div>
    <div class="col-md-4">
        <section id="socialLoginForm">
            @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
        </section>
    </div>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

اگر به صفحه ورود بروید خواهید دید که لینک فراموشی رمز عبور(Forgot your password?) نمایش داده می‌شود.

ارسال دوباره ایمیل فعال سازی

بعضی مواقع ممکن هست کاریر ایمیل فعال سازی دریافت نکند و یا به اشتباه آن را حذف کنید. در این صورت نیاز هست که کاربر دوباره امکان درخواست کد فعال سازی ایمیل را داشته باشد. در ادامه چگونگی فعال سازی این امکان را خواهیم دید.

برای ارسال دوباره لینک فعال سازی تابع Login را به شکل زیر تغییر دهید:

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    // Require the user to have a confirmed email before they can log on.
    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");

            // Uncomment to debug locally  
            // ViewBag.Link = callbackUrl;
            ViewBag.errorMessage = "You must have a confirmed email to log on. "
                                    + "The confirmation token has been resent to your email account.";
            return View("Error");
        }
    }

    // This doesn't count login failures towards account lockout
    // To enable password failures to trigger account lockout, change to shouldLockout: true
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

مرا به یاد داشته باش

این امکان به صورت پیش‌فرض در Asp.net mvc فعال می باشد. هنگام ورود به سایت یک token در کوکی کاربر ذخیره می‌شود. وقتی روی Remember me? کلیک شود یک تاریخ انتضا که به طور پیش‌فرض ۳۰ دقیقه است روی کوکی اعمال می‌شود. بعد از پایان تاریخ انتضا، کوکی حذف شده و کاربر به صفحه ورود منتقل می شود و باید وارد سایت شود. اگر تاریخ انقضای آن تمام نشده باشد نیازی به وارد کردن اطلاعات کاربری نیست و کاربر در سایت باقی می‌ماند.

برای تغییر مدت زمان انتضای کوکی به Startup.Auth.cs مراجعه کرده و مقدار validateInterval را مانند زیر تغییر دهید:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromDays(14),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});   

در Login وقتی روی Remember me? کلیک نشود مقدار false ارسال می‌شود ولی وقتی کلیک کنید دو مقدار ارسال می‌شود، مقادیر false,true.

مرا به یاد داشته باش

مرا به یاد داشته باش

برای حل این مشکل کد زیر را در انتهای صفحه اضافه کنید:

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script>
        $('[name="RememberMe"][type=hidden]').remove();
    </script>
}

تاریخ انقضا برای لینک فعال سازی

برای افزایش امنیت سایت، لینک فعال سازی باید در یک بازه‌ی زمانی کوتاه کلیک شود و خارج از آن باید غیر فعال شود. به همین منظور به IdentityConfig.cs مراجعه کنید و انتهای تابع Create را به شکل زیر تغییر دهید:

manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
    manager.UserTokenProvider =
        new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"))
        {
            TokenLifespan = TimeSpan.FromHours(3)
        };
}
return manager;

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت

کپچا معکوس تست تورینگ، یک آزمون برای تشخیص رایانه از انسان توسط رایانه است. این آزمون می تواند حالت های متنوعی داشته باشد و باید به صورتی باشد که ربات‌ها نتوانند به آن پاسخ دهند. کپچا یا کد امنیتی به صورت متداول از ترکیب حروف و اعداد تشکیل شده و به صورت ناواضح و کج به نمایش در می‌ِآید که فقط انسان بتواند آن را تشخیص دهد.

شرکت گوگل به تازگی کپچای جدیدی به نام No CAPTCHA reCAPTCHA را طراحی کرده است که روش کار آن با سایر کپچاها متفاوت است.

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت

در این پروژه ما از این کپچا استفاده خواهیم کرد. برای استفاده از این کپچا نیاز به دو کلید داریم که بایستی از سایت گوگل دریافت نمود. به صفحه کپچای گوگل  مراجعه کنید و روی Get reCaptcha کلیک کنید.

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت -2

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت -۲

صفحه جدید برای شما به نمایش در می‌آید. در قسمت Register a new site اطلاعات لازم را وارد نمایید.

در قسمت Label یک عنوان برای سایت خود وارد نمایید. در قسمت Domains آدرس پروژه خود را وارد می کنیم و در انتها یک ایمیل مالک پروژه را وارد می‌نماییم.

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت-3

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت-۳

در انتها روی دکمه Register کلیک می کنیم. در قسمت Adding reCAPTCHA to your site کلیک کنید. دو کلید Site key کلید عمومی و Secret key کلید خصوصی را دریافت می‌کنید. کلیک خصوصی نیاید به هیچ عنوان فاش شود.

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت-4

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت-۴

کدها را کپی و در web.config قرار دهید:

</connectionStrings>
  <appSettings>
    <!-- Google recaptcha-->
    <add key="recaptchaPublicKey" value=" reCaptcha Site Key " />
    <add key="recaptchaPrivateKey" value=" reCaptcha Secret Key" />
  </appSettings>
  <system.web>

نخست برای نمایش کپچا در مسیر  Helpers یک کلاس با نام HtmlHelpers ایجاد و کد زیر را در آن قرار دهید:

public static class HtmlHelpers
{
    public static IHtmlString ReCaptcha(this HtmlHelper helper)
    {
        var sb = new StringBuilder();
        string publickey = WebConfigurationManager.AppSettings["RecaptchaPublicKey"];
        sb.AppendLine("<script type=\"text/javascript\" " +
                        "src='https://www.google.com/recaptcha/api.js'></script>");
        sb.AppendLine("");
        sb.AppendLine("<div class=\"g-recaptcha\" data-sitekey=\"" + publickey + "\"></div>");
        return MvcHtmlString.Create(sb.ToString());
    }
}

به مسیر Views/Account/Login.cshtml رفته و کد زیر را به انتهای آن اضافه کنید:

<div class="form-group">
    <label class="col-md-2 control-label">Captcha</label>
    <div class="col-md-10">
        @Html.ReCaptcha()
    </div>
</div>

همچنین این کد را برای Register.cshtml و ForgotPassowrd.cshtml نیز اضافه نمایید. به مراجعه به این صفحات مشاهده می‌کنید کپچا نمایش داده می‌شود. حال باید کاربر به کپچا پاسخ دهد و در صورتی که روبات تشخیص داده شد یا کاربر نتوانست به آن پاسخ دهد باید یک خطا به او نمایش داده شود. برای این کار، در مسیر Filters یک کلاس با نام CaptchaValidatorAttribute ایجاد کنید:

public class CaptchaValidatorAttribute : FilterAttribute, IActionFilter
{
    private const string RESPONSE_FIELD_KEY = "g-recaptcha-response";


    private static bool Validate(string mainresponse, string privatekey)
    {
        try
        {
            var req = (HttpWebRequest)WebRequest.Create
            ("https://www.google.com/recaptcha/api/siteverify?secret=" +
            privatekey + "&response=" + mainresponse);

            WebResponse response = req.GetResponse();

            using (var readStream = new StreamReader(response.GetResponseStream()))
            {
                string jsonResponse = readStream.ReadToEnd();

                var jobj = JsonConvert.DeserializeObject<JsonResponseObject>(jsonResponse);

                return jobj.success;
            }
        }
        catch (Exception)
        {
            return false;
        }
    }

    private class JsonResponseObject
    {
        public bool success { get; set; }
        [JsonProperty("error-codes")]
        public List<string> errorcodes { get; set; }
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var validateCaptcha = false;

        if (filterContext.RequestContext.HttpContext.Request[RESPONSE_FIELD_KEY] != null)
        {
            string privatekey = WebConfigurationManager.AppSettings["recaptchaPrivateKey"];
            string response = filterContext.RequestContext.HttpContext.Request[RESPONSE_FIELD_KEY];
            validateCaptcha = Validate(response, privatekey);
        }

        if (!validateCaptcha)
        {
            ((Controller)(filterContext.Controller)).ModelState.AddModelError("", "Captcha not valid");
        }

    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        //throw new NotImplementedException();
    }
}

وقتی فرم حاوی یک کپچا ارسال می‌شود در Form Data آن یک فیلد g-recaptcha-response افزوده می‌شود که حاوی کدی است برای شناسایی کاربر.

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت-5

استفاده از کد امنیتی Recaptcha برای افزایش امنیت سایت-۵

این کد توسط فیلتر CaptchaValidatorAttribute در سمت سرور دریافت و توسط تابع Validate بررسی می‌شود. از یک فیلتر استفاده شده تا اطلاعات دریافتی قبل از ورود به تابع مورد بررسی قرار گیرد. پس در ابتدای تابع Login فیلتر [CaptchaValidator] را اضافه می‌کنیم:

//
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        [CaptchaValidator]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)

همچنین این فیلتر را در ابتدای توابع Register و ForgotPassowrd نیز وارد نمایید. حال وقتی کاربر کپچا را وارد ننماید فیلتر یک خطا به ModelState می‌افزاید و چون در ابتدای توایع، شرط ما ModelState.IsValid است پس خطا به view ارسال و نمایش داده می‌شود.

جلوگیری از حملات Brute-force

در این نوع حمله، هکر سعی می‌کند تا با آزمون تست و خطا رمز عبور را حدس بزند. او با فرستادن تعداد زیادی رمز عبور و نام کاربری اقدام به یافتن رمز عبور می‌کند. یک روش جلوگیری از این کار استفاده از رمز عبور پیچیده و طولانی و تعویض هرچند موقع آن است که بستگی به کاربر دارد. راه دیگر آن این است که پس از چند با وارد کردن رمز عبور نادرست حساب کاربر برای یک مدت مشخص مسدود کرد تا در فرایند اعتبار سنجی تاخیر ایجاد کنیم. برای فعال شدن این خصوصیت در Account controller تابع Login را به شکل زیر تغییر می دهیم:

// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[CaptchaValidator]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    var gUser = await UserManager.FindByEmailAsync(model.Email);
    if (gUser == null)
    {
        ModelState.AddModelError("", "Invalid login attempt.");
        return View(model);
    }

    var userName = gUser.UserName;
    var userId = gUser.Id;

    // Require the user to have a confirmed email before they can log on.
    // var user = await UserManager.FindByNameAsync(model.Email);
    var user = await UserManager.FindAsync(userName, model.Password);

    // When a user is lockedout, this check is done to ensure that even if the credentials are valid
    // the user can not login until the lockout duration has passed
    if (await UserManager.IsLockedOutAsync(userId))
    {
        ModelState.AddModelError("", string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", UserManager.MaxFailedAccessAttemptsBeforeLockout));
        return View(model);
    }
    // if user is subject to lockouts and the credentials are invalid
    // record the failure and check if user is lockedout and display message, otherwise,
    // display the number of attempts remaining before lockout
    else if (await UserManager.GetLockoutEnabledAsync(userId) && user == null)
    {
        // Record the failure which also may cause the user to be locked out
        await UserManager.AccessFailedAsync(gUser.Id);

        string message;

        if (await UserManager.IsLockedOutAsync(gUser.Id))
        {
            message = string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", UserManager.DefaultAccountLockoutTimeSpan);
        }
        else
        {
            int accessFailedCount = await UserManager.GetAccessFailedCountAsync(gUser.Id);

            int attemptsLeft =
                Convert.ToInt32(
                    UserManager.MaxFailedAccessAttemptsBeforeLockout) -
                accessFailedCount;

            message = string.Format(
                "Invalid credentials. You have {0} more attempt(s) before your account gets locked out.", attemptsLeft);

        }

        ModelState.AddModelError("", message);
        return View(model);
    }

    if (user != null)
    {
        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
            // Uncomment to debug locally  
            // ViewBag.Link = callbackUrl;

            ViewBag.errorMessage = "You must have a confirmed email to log on. "
                                    + "The confirmation token has been resent to your email account.";
            return View(model);
        }
    }

    // This doesn't count login failures towards account lockout
    // To enable password failures to trigger account lockout, change to shouldLockout: true
    SignInStatus result =
        await SignInManager.PasswordSignInAsync(userName, model.Password, model.RememberMe, shouldLockout: true);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, model.RememberMe });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

با فعال کردن shouldLockout این خصوصیت نیز فعال می‌شود. سپس باید بررسی کنیم که حساب کاربری مسدود نباشد. اگر مسدود باشد پیغام خطایی مبنی بر مدت زمان مسدود بودن حساب کاربر را به او نشان می دهیم. هر کاربر به صورت محدود می‌تواند کلمه عبور اشتباه را وارد نمایید. برای تغییر این محدودیت به مسیر App_Start/IdentityConfig.cs رفته و تغییر زیر را اعمال کنید:

// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(30);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;

به طور پیش‌فرض بعد از ۵ بار تلاش ناموفق حساب کاربر مسدود به مدت ۳۰ دقیقه مسدود می‌شود. باید تعداد تلاش‌های ناموفق کاربر و حداکثر تلاش ممکن نیز به کاربر اطلاع داده شود. این مقدار پس از دریافت در متغیر accessFailedCount ذخیره و به کار نمایش داده می‌شود. در صورتی که کاربر مسدود نباشد عملیات اعتبار‌سنجی به روال عادی خود پیش خواهد رفت.

برگرفته از مجموعه مقالاتی از سایت های زیر:

Microsoft

Stackoverflow

CodeProject

 

پایان

 

 

 

 

0
0


هرگونه انتشار مطالب اختصاصي و محصولات اين سايت بجز با درج لينک مستقيم شرعا حرام بوده و پيگرد قانوني دارد.
طبق ماده 12 فصل سوم قانون جرائم رايانه هرگونه کپي برداري ممنوع بوده و پيگرد قانوني دارد. براي اطلاعات بيشتر کليک کنيد.

درباره‌ی مدیر سایت

در اردیبهشت سالی از سال های خدا بدنیا آمدم.درس خواندم و مهندس شدم.ازآنجایی که علاقه شدیدی به برنامه نویسی و ترجمه وآموزش داشتم در راستای اهدافم این وبسایت رو راه اندازی کردم تا دین خود را به جامعه برنامه نویسی ادا کرده باشم.

یک دیدگاه

  1. بسیار عالی و کمک کننده بود ممنونم

دیدگاه خود را بنویسید

آدرس پست الکترونیک شما منتشر نخواهد شد.خانه های ضروری نشانه گذاری شده اند. *

*


× چهار = 16