نحوه ایجاد یک تصویر امنیتی (Captcha) با حروف فارسی در ASP.Net MVC
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هفت دقیقه

در این مطلب، سعی خواهیم کرد تا همانند تصویر امنیتی این سایت که موقع ورود نمایش داده می‌شود، یک نمونه مشابه به آنرا در ASP.Net MVC ایجاد کنیم. ذکر این نکته ضروری است که قبلا آقای پایروند در یک مطلب دو قسمتی کاری مشابه را انجام داده بودند، اما در مطلبی که در اینجا ارائه شده سعی کرده ایم تا تفاوتهایی را با مطلب ایشان داشته باشد.

همان طور که ممکن است بدانید، اکشن متدها در کنترلرهای MVC می‌توانند انواع مختلفی را برگشت دهند که شرح آن در مطالب این سایت به مفصل گذشته است. یکی از این انواع، نوع ActionResult می‌باشد. این یک کلاس پایه برای انواع برگشتی توسط اکشن متدها مثل JsonResult، FileResult می‌باشد. (اطلاعات بیشتر را اینجا بخوانید) اما ممکن است مواقعی پیش بیاید که بخواهید نوعی را توسط یک اکشن متد برگشت دهید که به صورت توکار تعریف نشده باشد. مثلا زمانی را در نظر بگیرید که بخواهید یک تصویر امنیتی را برگشت دهید. یکی از راه حل‌های ممکن به این صورت است که کلاسی ایجاد شود که از کلاس پایه ActionResult ارث بری کرده باشد. بدین صورت:

using System;
using System.Web.Mvc;

namespace MVCPersianCaptcha.Models
{
    public class CaptchaImageResult : ActionResult 
    {
        public override void ExecuteResult(ControllerContext context)
        {
            throw new NotImplementedException();
        }
    }
}
همان طور که مشاهده می‌کنید، کلاسی به اسم CaptchaImageResult تعریف شده که از کلاس ActionResult ارث بری کرده است. در این صورت باید متد ExecuteResult را override کنید. متد ExecuteResult به صورت خودکار هنگامی که از CaptchaImageResult به عنوان یک نوع برگشتی اکشن متد استفاده شود اجرا می‌شود. به همین خاطر باید تصویر امنیتی توسط این متد تولید شود و به صورت جریان (stream)  برگشت داده شود

کدهای اولیه برای ایجاد یک تصویر امنیتی به صورت خیلی ساده از کلاس‌های فراهم شده توسط +GDI ، که در دات نت فریمورک وجود دارند استفاده خواهند کرد. برای این کار ابتدا یک شیء از کلاس Bitmap با دستور زیر ایجاد خواهیم کرد:
// Create a new 32-bit bitmap image.
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
پارامترهای اول و دوم به ترتبی عرض و ارتفاع تصویر امنیتی را مشخص خواهند کرد و پارامتر سوم نیز فرمت تصویر را بیان کرده است. Format32bppArgb یعنی یک تصویر که هر کدام از پیکسل‌های آن 32 بیت فضا اشغال خواهند کرد ، 8 بیت اول میزان آلفا، 8 بیت دوم میزان رنگ قرمز، 8 بیت سوم میزان رنگ سبز، و 8 تای آخر نیز میزان رنگ آبی را مشخص خواهند کرد 

سپس شیئی از نوع Graphics برای انجام عملیات ترسیم نوشته‌های فارسی روی شیء bitmap ساخته می‌شود:
// Create a graphics object for drawing.
Graphics gfxCaptchaImage = Graphics.FromImage(bitmap);
خصوصیات مورد نیاز ما از gfxCaptchaImage را به صورت زیر مقداردهی می‌کنیم:
gfxCaptchaImage.PageUnit = GraphicsUnit.Pixel;
gfxCaptchaImage.SmoothingMode = SmoothingMode.HighQuality;
gfxCaptchaImage.Clear(Color.White);
واحد اندازه گیری به پیکسل، کیفیت تصویر تولید شده توسط دو دستور اول، و در دستور سوم ناحیه ترسیم با یک رنگ سفید پاک می‌شود.

سپس یک عدد اتفاقی بین 1000 و 9999 با دستور زیر تولید می‌شود:
// Create a Random Number from 1000 to 9999
int salt = CaptchaHelpers.CreateSalt();
متد CreateSalt در کلاس CaptchaHelpers قرار گرفته است، و نحوه پیاده سازی آن بدین صورت است:
public int CreateSalt()
{
   Random random = new Random();
   return random.Next(1000, 9999);
}
سپس مقدار موجود در salt را برای مقایسه با مقداری که کاربر وارد کرده است در session قرار می‌دهیم:
HttpContext.Current.Session["captchastring"] = salt;
سپس عدد اتفاقی تولید شده باید تبدیل به حروف شود، مثلا اگر عدد 4524 توسط متد CreateSalt تولید شده باشد، رشته "چهار هزار و پانصد و بیست و چهار" معادل آن نیز باید تولید شود. برای تبدیل عدد به حروف، آقای نصیری کلاس خیلی خوبی نوشته اند که چنین کاری را انجام می‌دهد. ما نیز از همین کلاس استفاده خواهیم کرد:
string randomString = (salt).NumberToText(Language.Persian);
در دستور بالا، متد الحاقی NumberToText با پارامتر Language.Persian وظیفه تبدیل عدد salt را به حروف فارسی معادل خواهد داشت.

به صورت پیش فرض نوشته‌های تصویر امنیتی به صورت چپ چین نوشته خواهند شد، و با توجه به این که نوشته ای که باید در تصویر امنیتی قرار بگیرد فارسی است، پس بهتر است آنرا به صورت راست به چپ در تصویر بنویسیم، بدین صورت:
// Set up the text format.
var format = new StringFormat();
int faLCID = new System.Globalization.CultureInfo("fa-IR").LCID;
format.SetDigitSubstitution(faLCID, StringDigitSubstitute.National);
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Near;
format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
و همچنین نوع و اندازه فونت که در این مثال tahoma می‌باشد:
// Font of Captcha and its size
Font font = new Font("Tahoma", 10);
خوب نوشته فارسی اتفاقی تولید شده آماده ترسیم شدن است، اما اگر چنین تصویری تولید شود احتمال خوانده شدن آن توسط روبات‌های پردازش گر تصویر شاید زیاد سخت نباشد. به همین دلیل باید کاری کنیم تا خواندن این تصویر برای این روبات‌ها سخت‌تر شود، روش‌های مختلفی برای این کار وجود دارند: مثل ایجاد نویز در تصویر امنیتی یا استفاده از توابع ریاضی سینوسی و کسینوسی برای نوشتن نوشته‌ها به صورت موج. برای این کار اول یک مسیر گرافیکی در تصویر یا موج اتفاقی ساخته شود و به شیء gfxCaptchaImage نسبت داده شود. برای این کار اول نمونه ای از روی کلاس GraphicsPath ساخته می‌شود،
// Create a path for text 
GraphicsPath path = new GraphicsPath();
و با استفاده از متد AddString ، رشته اتفاقی تولید شده را با فونت مشخص شده، و تنظیمات اندازه دربرگیرنده رشته مورد نظرر، و تنظیمات فرمت بندی رشته را لحاظ خواهیم کرد.
path.AddString(randomString, 
                font.FontFamily, 
                (int)font.Style, 
                (gfxCaptchaImage.DpiY * font.SizeInPoints / 72), 
                new Rectangle(0, 0, width, height), format);
با خط کد زیر شیء path را با رنگ بنقش با استفاده از شیء gfxCaptchaImage روی تصویر bitmap ترسیم خواهیم کرد:
gfxCaptchaImage.DrawPath(Pens.Navy, path);
برای ایجاد یک منحنی و موج از کدهای زیر استفاده خواهیم کرد:
//-- using a sin ware distort the image
int distortion = random.Next(-10, 10);
using (Bitmap copy = (Bitmap)bitmap.Clone())
{
          for (int y = 0; y < height; y++)
          {
              for (int x = 0; x < width; x++)
              {
                  int newX = (int)(x + (distortion * Math.Sin(Math.PI * y / 64.0)));
                  int newY = (int)(y + (distortion * Math.Cos(Math.PI * x / 64.0)));
                  if (newX < 0 || newX >= width) newX = 0;
                 if (newY < 0 || newY >= height) newY = 0;
                 bitmap.SetPixel(x, y, copy.GetPixel(newX, newY));
              }
         }
 }
موقع ترسیم تصویر امنیتی است:
//-- Draw the graphic to the bitmap
gfxCaptchaImage.DrawImage(bitmap, new Point(0, 0));

gfxCaptchaImage.Flush();
تصویر امنیتی به صورت یک تصویر با فرمت jpg به صورت جریان (stream) به مرورگر باید فرستاده شوند:
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "image/jpeg";
bitmap.Save(response.OutputStream, ImageFormat.Jpeg);
و در نهایت حافظه‌های اشغال شده توسط اشیاء فونت و گرافیک و تصویر امنیتی آزاد خواهند شد:
// Clean up.
font.Dispose();
gfxCaptchaImage.Dispose();
bitmap.Dispose();
برای استفاده از این کدها، اکشن متدی نوشته می‌شود که نوع CaptchaImageResult را برگشت می‌دهد:
public CaptchaImageResult CaptchaImage()
{
     return new CaptchaImageResult();
}
اگر در یک View خصیصه src یک تصویر به آدرس این اکشن متد مقداردهی شود، آنگاه تصویر امنیتی تولید شده نمایش پیدا می‌کند:
<img src="@Url.Action("CaptchaImage")"/>
بعد از پست کردن فرم مقدار text box تصویر امنیتی خوانده شده و با مقدار موجود در session مقایسه می‌شود، در صورتی که یکسان باشند، کاربر می‌تواند وارد سایت شود (در صورتی که نام کاربری یا کلمه عبور خود را درست وارد کرده باشد) یا اگر از این captcha در صفحات دیگری استفاده شود عمل مورد نظر می‌تواند انجام شود. در مثال زیر به طور ساده اگر کاربر در کادر متن مربوط به تصویر امنیتی مقدار درستی را وارد کرده باشد یا نه، پیغامی به او نشان داده می‌شود.  
[HttpPost]
public ActionResult Index(LogOnModel model)
{
      if (!ModelState.IsValid) return View(model);

      if (model.CaptchaInputText == Session["captchastring"].ToString()) 
             TempData["message"] = "تصویر امنتی را صحیح وارد کرده اید";
      else 
             TempData["message"] = "تصویر امنیتی را اشتباه وارد کرده اید";

      return View();
}

کدهای کامل مربوط به این مطلب را به همراه یک مثال از لینک زیر دریافت نمائید:
MVC-Persian-Captcha
  • #
    ‫۱۱ سال و ۴ ماه قبل، شنبه ۴ خرداد ۱۳۹۲، ساعت ۰۰:۰۶
    ممنون. میشه قسمت بررسی نهایی در اکشن متد رو هم کپسوله کرد (چیزی شبیه به امکانات AOP سرخود در MVC). مثلا یک ویژگی جدید به نام ValidateCaptcha درست کرد که به اکشن متد اعمال شود و کار بررسی صحت اطلاعات ورودی مخصوص Captcha رو انجام و نهایتا اطلاعات ModelState رو بر اساس اطلاعات ورودی و Session ایی که در اینجا تعریف شده، به روز کنه:
        [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
        public sealed class ValidateCaptchaAttribute : ActionFilterAttribute
        {
            public override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                var controllerBase = filterContext.Controller;
    
                var captchaInputTextProvider = controllerBase.ValueProvider.GetValue("CaptchaInputText");
                if (captchaInputTextProvider == null)
                {
                    controllerBase.ViewData.ModelState.AddModelError("CaptchaInputText", "لطفا تصویر امنیتی را وارد کنید");
                    base.OnActionExecuting(filterContext);
                    return;
                }
                var inputText = captchaInputTextProvider.AttemptedValue;
    
                if (inputText != Session["captchastring"].ToString())
                   controllerBase.ViewData.ModelState.AddModelError("CaptchaInputText", "تصویر امنیتی را اشتباه وارد کرده اید");
             } 
         }
    به این صورت (با استفاده از ویژگی فوق) همان بررسی متداول ModelState.IsValid در یک اکشن متد کافی خواهد بود.
    • #
      ‫۱۱ سال و ۴ ماه قبل، شنبه ۴ خرداد ۱۳۹۲، ساعت ۰۴:۵۷
      بسیار عالی و سازنده، خیلی نکته مفیدی بود
    • #
      ‫۱۱ سال و ۴ ماه قبل، شنبه ۴ خرداد ۱۳۹۲، ساعت ۱۹:۴۷

      سلام

      اگر کد شما را گسترش دهیم و به جای پیغام‌های خطای ارسال شده، مقادیر Attribute‌ها یا متادیتاهایی مثل Required را خواند و مستقیم پیغامی به کنترل و ModelState پاس نداد، فکر می‌کنم کامل‌تر باشه .

      • #
        ‫۱۱ سال و ۴ ماه قبل، شنبه ۴ خرداد ۱۳۹۲، ساعت ۲۱:۰۸
        حالت کاملتر زمانی است که در سازنده کلاس ویژگی، این خطاها رو دریافت کنید و به کاربر اجازه بدید، پیغام‌های دلخواهی رو تنظیم کنه. چون Required فقط یک حالت است. حالت بعدی عدم تساوی مقدار ورودی با مقدار اصلی Captcha است که نیاز به پیغام دومی دارد.
  • #
    ‫۱۱ سال و ۴ ماه قبل، شنبه ۴ خرداد ۱۳۹۲، ساعت ۱۸:۲۷

    ممنون - مطلب کاربردی و مفیدی بود

    شخصا این Captcha را به انواع دیگر ترجیح میدم چرا که:

    1- شخصی سازی شده

    2- ارجاع به سایت واسطی نداره

    3- هیچ گونه سرباری نداره

    4- اعمال هر گونه تغییر و اصلاح مناسب با فرهنگ و کشور و یا حتی نوع سایت قابل انجام است

    بازهم تشکر می‌کنم

    • #
      ‫۱۱ سال و ۴ ماه قبل، یکشنبه ۵ خرداد ۱۳۹۲، ساعت ۱۳:۱۱
      خواهش می‌کنم دوست عزیز. لطف دارید

      موارد بیشتری که می‌توان به آن افزود:
      استفاده از فونت‌ها به صورت اتفاقی برای نوشتن متن تصویر امنیتی،
      دادن رنگ‌ها به متن و پس زمینه به صورت اتفاقی به تصویر امنیتی،
      معتبر نبودن session نگهدارنده عدد تصویر امنیتی بعد از یک میزان مشخص،
      و غیره
  • #
    ‫۱۱ سال و ۴ ماه قبل، دوشنبه ۶ خرداد ۱۳۹۲، ساعت ۲۳:۳۴
    سلام و مرسی از آموزش خوب و کاربردیتون
    اما یکی از گزینه‌های چک لیست برنامه‌های ASP.NET MVC که آقای نصیری فرمودند عدم استفاده از سشن در برنامه‌ها بود و استفاده از کوکی‌های رمزنگاری شده پیشنهاد شده بود.
    • #
      ‫۱۱ سال و ۴ ماه قبل، سه‌شنبه ۷ خرداد ۱۳۹۲، ساعت ۰۱:۳۲
      سلام. ممنون از نظر شما
      چک لیست رو مطالعه کردم و عملا هم استفاده کردم.
      استفاده از سشن راحت‌تر هست و به همین دلیل اون رو انتخاب کردم. نسخه بعدی این تصویر امنیتی رو با استفاده از نظرات همین مطلب ارائه می‌کنم.
  • #
    ‫۱۱ سال و ۴ ماه قبل، چهارشنبه ۱۵ خرداد ۱۳۹۲، ساعت ۱۰:۵۸
    سلام
    نسخه بعدی این تصویر امنیتی رو از لینک زیر دریافت نمائید:
    MVCPersianCaptcha-2.zip

    امکاناتی که اضافه کردم:
    - استفاده از کوکی رمزنگاری شده جهت ذخیره کردن مقدار عدد معادل تصویر امنیتی
    - اضافه کردن ویژگی ValidateCaptcha جهت تعیین اعتبار کوکی و مقداری که کاربر وارد کرده
    - اضافه کردن نویزهای اتفاقی
    - تعیین یک میزان 30 ثانیه ای (قابل تغییر است) جهت معتبر بودن مقدار ارسالی توسط کاربر
    - ایجاد قابلیت تازه سازی (refresh) تصویر امنیتی
    - تغییر کلید رمزنگاری و رمزگشایی اطلاعات به ازاء هر روز
    و غیره
  • #
    ‫۱۱ سال و ۴ ماه قبل، چهارشنبه ۲۲ خرداد ۱۳۹۲، ساعت ۰۳:۲۲
    پوشه‌های packages، bin، و obj از نسخه‌های اول و دوم این تصویر امنیتی حذف شده اند تا حجم فایلهای مربوطه کمتر شوند. به همین دلیل، بعد از اجرای هر کدام از این برنامه‌ها خطایی صادر می‌شود مبنی بر این که بسته ای به اسم Newtonsoft.Json در پروژه وجود ندارد. لطفا برای حل این مشکل به فایل Global.asax.cs مراجعه کنید و خط کد زیر را از آن حذف کنید: (این خط کد در این پروژه غیر ضروری است و نیازی به آن نیست)
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    روش دیگر برای حل این نوع مشکلات، در مطلب بازسازی کامل پوشه packages بسته‌های NuGet به صورت خودکاربیان شده است.
  • #
    ‫۱۱ سال و ۳ ماه قبل، سه‌شنبه ۱۱ تیر ۱۳۹۲، ساعت ۱۵:۰۴
    با سلام و تشکر از همه دوستان
    بنده در اولین استفاده یک تغییر داشتم که خواستم بقیه هم از آن استفاده کنند:
        public ValidateCaptchaAttribute()
            {
                ErrorWasHappened = "خطایی اتفاق افتاده است";
                CaptchaCodeIsRequired = "لطفا کد امنیتی را وارد کنید";
                CaptchaCodeIsIncorrect = "کد امنیتی را اشتباه وارد کرده اید";
                CookieMustEnabled = "باید ابتدا قابلیت کوکی‌ها را در مرورگر خود فعال کنید";
                ExpireTimeCaptchaCodeBySeconds = 60;
                TimeIsExpired = string.Format("حداکثر مهلت وارد کردن کد امنیتی {0} ثانیه است",ExpireTimeCaptchaCodeBySeconds.ToString());
    
            }
    باز ممنون!
  • #
    ‫۱۱ سال و ۳ ماه قبل، شنبه ۱۵ تیر ۱۳۹۲، ساعت ۰۲:۰۸
    با سلام.
    سپاس از مقاله خوبتون. 
    در کلاینت همه چی درست کار میکند ولی وقتی آنرا درون هاست واقعی publish میکنم، تصویر نمایش داده نمی‌شود و خطایی که توسط elmah لاگ می‌شود به صورت زیر است:
    The system cannot find the file specified " source="mscorlib" detail="System.Security.Cryptography.CryptographicException: The system cannot find the file specified.

    با تشکر.
    • #
      ‫۱۱ سال و ۳ ماه قبل، شنبه ۱۵ تیر ۱۳۹۲، ساعت ۰۳:۳۲
      <system.web>
       <trust level="Medium" originUrl=".*" />

      شاید هاست شما مدیوم تراست هست. برای تست روی لوکال این تنظیم بالا رو به وب کانفیگ اضافه کنید تا خطا رو بتونید لوکال دیباگ کنید.

      • #
        ‫۱۱ سال و ۳ ماه قبل، شنبه ۱۵ تیر ۱۳۹۲، ساعت ۰۳:۴۶
        سپاس از پاسخ شما.
        ولی با اضافه کردن آن ، همواره خطای زیر رخ می‌دهد:
        The application attempted to perform an operation not allowed by the security policy.  To grant this application the required permission please contact your system administrator or change the application's trust level in the configuration file

    • #
      ‫۱۱ سال و ۳ ماه قبل، شنبه ۱۵ تیر ۱۳۹۲، ساعت ۱۰:۳۷
      سلام، از نظر شما متشکرم

      همون طور که آقای محسن خان گفت، احتمالا هاست شما medium trust هست. اما رو کامپیوتر خودتون full trust برنامه نویسی می‌کنید.

      چون هویت کاربر هنوز مشخص نشده پیغامی مبنی بر این لاگ میشه که اسمبلی mscorlib وجود نداره. در واقع وجود داره ولی نه برای کاربر anonymous ! همچنین این به دلیل medium trust بودن هم میتونه باشه. برای حل این مشکل کارهای زیر رو انجام بدین:

      1) به فایل web.config برین و کد زیر رو اضافه کنید:
      <trust level="Full" originUrl=".*" />

      2) باید تغییری رو در متدهای الحاقی Encrypt و Decrypt بدین، که از این متدها برای رمزنگاری و رمزگشایی محتوای کوکی تصویر امنیتی استفاده میشه. قبل از هر کدوم از این متدها flag زیر رو اضافه کنید:

      [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Assert, Unrestricted = true)]

      همچنین، یک خط داخل بدنه هر کدوم از متدهای الحاقی Encrypt و Decrypt هست، منظورم این خط کد هست:
      var cspp = new CspParameters { KeyContainerName = key };
      که باید به خط کد زیر تبدیل بشه:
      var cspp = new CspParameters { KeyContainerName = key, Flags = CspProviderFlags.UseMachineKeyStore };

      • #
        ‫۱۱ سال و ۳ ماه قبل، شنبه ۱۵ تیر ۱۳۹۲، ساعت ۱۳:۳۷
        بله کار کرد. متشکرم. 
      • #
        ‫۱۰ سال قبل، سه‌شنبه ۲۵ شهریور ۱۳۹۳، ساعت ۱۴:۳۸
        من هم همین مشکل را داشتم. قبل از این که توضیحات اینجا را ببینم، msdn را دیده بودم:

        http://social.msdn.microsoft.com/Forums/vstudio/en-US/7ea48fd0-8d6b-43ed-b272-1a0249ae490f/systemsecuritycryptographycryptographicexception-the-system-cannot-find-the-file-specified?forum=clr

        روشی که آنجا گفته بود باید application pool را از integrated به classic می‌بردی و از network service برای identity استفاده می‌کردی. هر چند که این روش هم جواب داد ولی روش توضیح داده اینجا، یعنی اصلاح کد خیلی بهتر است.
        البته در مورد من نیاز به اضافه کردن trust level به web.config نبود.
        • #
          ‫۸ سال و ۵ ماه قبل، دوشنبه ۱۶ فروردین ۱۳۹۵، ساعت ۲۱:۴۴
          ممنون بابت راهنماییتون؛ روش‌های قبلی مشکل من رو حل نکردن چرا؟ فقط توضیحات شما مشکلمو حل کرد. ولی تفاوت اینکه identity من الان روی network service با قبلش که روی app pool بوده چه فرقی داره؟ یعنی مشکل امنیتی ایجاد نمیکنه؟
          • #
            ‫۸ سال و ۵ ماه قبل، دوشنبه ۱۶ فروردین ۱۳۹۵، ساعت ۲۱:۵۸
            network service همان نام یوزر پیش فرض ASP.NET هست و سطح دسترسی آن بر روی سرور حداقل است.
      • #
        ‫۹ سال و ۸ ماه قبل، شنبه ۴ بهمن ۱۳۹۳، ساعت ۰۹:۴۸
        سلام
        من تمامی مراحل طی کردم اما متاسفانه روی سرور با اینکه فول تراست هست همین خطا دریافت میکنم
  • #
    ‫۱۱ سال و ۲ ماه قبل، شنبه ۲۹ تیر ۱۳۹۲، ساعت ۲۲:۵۹
    با سلام.
    برای اولین بار که فرمی لود می‌شود همواره حتی با وارد کردن مقدار صحیح متن کپچا باز هم خطای نادرست بودن می‌دهد. ولی وقتی یکبار بر روی رفرش مربوط به کپچا کلیک میکنم تا کپچای جدیدی تولید کند ، درست کار می‌کند؟ 
    • #
      ‫۱۱ سال و ۲ ماه قبل، چهارشنبه ۲ مرداد ۱۳۹۲، ساعت ۰۲:۲۱
      سلام دوست عزیز
      بابت تاخیر بوجود آمده در پاسخ دهی عذرخواهی می‌کنم.

      من همین کپچا رو دارم استفاده می‌کنم و این مشکلو هم باهاش ندارم. یک حدسی که می‌زنم اینه که شاید کوکی بار اول درست مقداردهی نشه. لطفا کل عملیات از تشکیل تصویر امنیتی، تا مقداری که درون کوکی قرار می‌گیره، و بعد رمز گزاری میشه ، تا مقداری که درون کوکی مرورگر ذخیره میشه ( برای مشاهده کوکی‌های مرورگر از این روش می‌تونید استفاده کنید) . سپس ببینید بعد از پست شدن فرم حاوی کپچا به سرور مقداری که از درون کوکی خونده میشه و می‌خواد decrypt بشه با مقداری که از اول ذخیره شده یکسان باشه.

      امیدوارم مشکلتون حل شه دوست عزیز.
    • #
      ‫۹ سال و ۱۰ ماه قبل، شنبه ۱ آذر ۱۳۹۳، ساعت ۱۸:۱۵
      یک مورد دیگه که باعث بروز این مشکل میشه افزونه فایرباگ هست. علتش رو نمی‌دونم اما در زمان فعال بودن فایرباگ دو بار درخواست برای تصویر امنیتی ارسال و مقدار درون کوکی با تصویری که می‌بینید متفاوت میشه.
      درحالی که در زمان خاموش بودن فایرباگ این مشکل وجود نداره.
  • #
    ‫۱۰ سال و ۹ ماه قبل، پنجشنبه ۲۸ آذر ۱۳۹۲، ساعت ۱۳:۳۳
    باسلام، این مطلب خیلی کاربردی و خوب بود، فقط نکته ای که در فرم Register پیش فرض MVC وجود داره، اینکه این View به کنترلر Account وصله که هنگامی که CaptchaImageResult را درون آن قرار می‌دهیم، در فرم Register اجرا نمی‌شود. می‌خواستم راهنمایی کنید که در این فرم چگونه باید عمل کنم. مرسی!
    • #
      ‫۱۰ سال و ۹ ماه قبل، پنجشنبه ۲۸ آذر ۱۳۹۲، ساعت ۱۳:۳۹
      ویژگی AllowAnonymous را بالای آن قرار دهید. همچنین Url.Action دو پارامتر نام کنترلر و نام اکشن متد را دریافت می‌کند (برای استفاده از آن در سایر Viewهایی که در کنترلر جاری نیستند).
      • #
        ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۲:۴۳
        با تشکر از راهنمایی شما، این مورد به خوبی در حالت localhost کار می‌کند و تغییر تصویر نیز به خوبی عمل می‌کند. هنگامی که سایت را Publish میکنم، تصاویر نمایش داده نمی‌شود. ممنون میشم راهنمایی کنید.
        • #
          ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۳:۲۷
          برای دیباگ کردن این مساله در سمت سرور بهتر است از ELMAH استفاده کنید. لاگ آن‌را بررسی کنید شاید جایی در پشت صحنه خطایی صادر می‌شود.
      • #
        ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۳:۵۷
        با سلام،  Elmah Log رو چک کردم، مشکل دسترسی داشت که در قسمت Web.config تگ Trust رو اضافه کردم و فعلاً ظاهر شد. اضافه کردن trust مشکل امنیتی ایجاد نمی‌کنه؟
        • #
          ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۴:۰۶
          کدوم قسمتش مشکل دسترسی داشت؟ چه خطایی گرفتید دقیقا؟ چه trust ایی رو اضافه کردید؟ (در هاستی که بتونید trust level رو تغییر بدید، یعنی عملا از لحاظ امنیتی درست تنظیم نشده و مداخل رو قفل نکرده)
          • #
            ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۴:۱۸

            پیغام:

            The system cannot find the file specified " source=" mscorlib "
            کد تراست
             < trust level = "Medium" originUrl = ".*" />
              • #
                ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۶:۳۰
                هنوز کامل باهاش سر و کله نزدم، ولی یک ثبت نام کردم دیدم داره درست کار می‌کنه. پس خطری نداره این کد.
                • #
                  ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۸:۲۶
                  trust level یعنی کد شما مثلا نتونه دایرکتوری فونت ویندوز رو روی سرور لیست کنه یا نتونه به کلیدهای رمزنگاری سطح ماشین دسترسی پیدا کنه. از این دید باید به آن نگاه کرد نه از دید کد خطرناک. این پروژه که سورس باز هست و می‌تونید کدهای آن را بررسی کنید.
                  • #
                    ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۸:۵۶
                    منظور من این نبود، منظورم خطری برای سایر Third Party API‌ها بود که دادن تراست لول خطری برای ای پی آی‌های دیگه نداشته باشه. در مورد این کد هیچ چیز مبهمی وجود نداره و هم توضیحات خوب داده شده بود و هم از متدهای رادست و قابل فهم استفاده شده بود. از راهنمایی‌های شما هم بسیار ممنونم.
              • #
                ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۱۹:۵۰
                با سلام.
                من از این کپچا در یک سرور استفاده کردم، بر روی پورت 80 هیچ خطایی نمیگیرم و تصویر امنیتی بدرستی نمایش داده می‌شود ولی روی پورت 8080 تصویر کپچا لود نمیشود و خطای زیر را در کنسول کروم لاگ میکند:

                 System.Security.Cryptography.CryptographicException: Object already exists
                Resource interpreted as Image but transferred with MIME type text/html: "http://site:8080/Error/Index?aspxerrorpath=/Shared/CaptchaImage"
                با تشکر.
                • #
                  ‫۱۰ سال و ۹ ماه قبل، یکشنبه ۱۵ دی ۱۳۹۲، ساعت ۲۱:۱۲

                  اگر به سرور دسترسی دارید این دستور را اجرا کنید:

                  aspnet_regiis -pa "SampleKeys" "NT AUTHORITY\NETWORK SERVICE"
  • #
    ‫۱۰ سال و ۶ ماه قبل، پنجشنبه ۷ فروردین ۱۳۹۳، ساعت ۱۰:۰۵
    من در یک پروژه می‌خواستم از این Captcha استفاده کنم. زمانی که صفحه اصلی لود میشه Captcha درست نمایش داده میشه ولی زمانی که کاربر از سایت SignOut می کنه دیگه محتوای Captcha نمایش داده نمی‌شود و اصلاً کنترولر CaptchaImage فراخوانی نمی‌شود ممنون میشم راهنمایی کنید 
    • #
      ‫۱۰ سال و ۶ ماه قبل، پنجشنبه ۷ فروردین ۱۳۹۳، ساعت ۱۶:۱۶
      اگر فراخوانی نمی‌شود، یعنی کش شده. برای رفع این مشکل، ویژگی زیر را بر روی اکشن متد CaptchaImage قرار دهید:
      [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
      برای بررسی دقیق‌تر از افزونه‌ی فایرباگ استفاده کنید.
  • #
    ‫۱۰ سال و ۲ ماه قبل، جمعه ۱۰ مرداد ۱۳۹۳، ساعت ۱۶:۱۸
    سلام
    میدونم پست نسبتا قدیمی!
    ولی من یه مورد پیدا کردم و اونم اینه که در صورتی که captcha رو درست بزنی و برید به صفحه جدید (مثلا بعد لاگین برید به صفحه مدیر) دوباره back بزنی همون مقدار رو می‌بینید و متاسفانه دوباره کار هم میکنه.
    کش رو هم غیر فعال کردم ولی تاثیری نداشت!
    اگر راه حلی دارد ممنون میشم در میان بگذارید.
      • #
        ‫۱۰ سال و ۲ ماه قبل، جمعه ۱۰ مرداد ۱۳۹۳، ساعت ۱۶:۴۲
        بله جواب نداد!
    • #
      ‫۱۰ سال و ۲ ماه قبل، جمعه ۱۰ مرداد ۱۳۹۳، ساعت ۱۷:۵۴
      1- فایل CaptchaImageResult.cs نگارش دوم را باز کنید.
      - در انتهای آن تغییر زیر را اعمال کنید:
      // در فایل CaptchaImageResult.cs
      HttpResponseBase response = context.HttpContext.Response;
      response.ContentType = "image/jpeg";
      context.HttpContext.DisableBrowserCache(); // این سطر جدید است
      bitmap.Save(response.OutputStream, ImageFormat.Jpeg);
      متد الحاقی DisableBrowserCache در مطلب غیرفعال کردن کش مرورگر در MVC ارائه شده.

      2- امضای متد Index کنترل Home نیاز به NoBrowserCache دارد (کل صفحه‌ی لاگین کش نشود):
          public class HomeController : Controller
          {
              [NoBrowserCache]
              public ActionResult Index()

      3- امضای متد CaptchaImage را در کنترلر Home به نحو زیر تغییر دهید (آدرس خاص تصویر نمایش داده شده، کش نشود):
      [NoBrowserCache]
      [OutputCache(Location = OutputCacheLocation.None, NoStore = true, Duration = 0, VaryByParam = "None")]
      public CaptchaImageResult CaptchaImage(string rndDate)
      سپس در محل استفاده در View به صورت زیر باید استفاده شود:
      <img src="@Url.Action("CaptchaImage", "Home", routeValues: new{ rdnDate = DateTime.Now.Ticks })"/>
  • #
    ‫۱۰ سال قبل، سه‌شنبه ۲۵ شهریور ۱۳۹۳، ساعت ۱۴:۴۰
    کد خیلی مفید و خوبی است. کاش آن را در github یا codeplex هم قرار می‌دادید تا همه به روز رسانی‌ها و pull requestهای احتمالی از همانجا انجام می‌شد. در هر صورت بابت این کد مفید تشکر می‌کنم.
  • #
    ‫۹ سال و ۸ ماه قبل، یکشنبه ۲۱ دی ۱۳۹۳، ساعت ۱۲:۴۴
    سلام؛ من از این روش استفاده می‌کنم. هر از چند وقت گیر میده که خطایی اتفاق افتاده؛ تریس میکنم، این خطا رو نشون میده:
     {"Error occurred while decoding OAEP padding."}
  • #
    ‫۹ سال و ۸ ماه قبل، سه‌شنبه ۷ بهمن ۱۳۹۳، ساعت ۱۳:۰۵
    با سلام خدمت کلیه دوستان و همکاران گرامی
    از اینکه نمی‌تونم جواب نظرات رو بدم عذرخواهی می‌کنم. (به دلیل خدمت سربازی :دی)
    بنا به پیشنهاد جناب نصیری و دیگر دوستان یک مخزن کد Github برای این تصویر امنیتی درست کردم. اصلاح باگ‌های برطرف شده اون هم تا اینجا طبق نظرات همین مطلب اعمال شدن. هر کی می‌خواد تو این پروژه مشارکت داشته باشه PR بفرسته .
  • #
    ‫۹ سال و ۶ ماه قبل، شنبه ۸ فروردین ۱۳۹۴، ساعت ۱۵:۰۹
    اگر بخواهم که کاربر با وارد کردن مستقیم آدرس ایجاد کپچا، کد آن اجرا نشود اضافه کردن کد زیر به ابتدای متد ExecuteResult  کلاس CaptchaImageResult درست هست؟ 
    البته در این کد Null reference می‌گیرم ولی خب ایجاد کپچا متوقف میشود. راه دیگه ای هست که بدون Null refrence جواب بدهد؟
    if (context.RequestContext.HttpContext.Request.UrlReferrer.AbsolutePath ==
                    context.RequestContext.HttpContext.Request.Url.AbsolutePath)
                    throw new InvalidOperationException();

    • #
      ‫۹ سال و ۶ ماه قبل، شنبه ۸ فروردین ۱۳۹۴، ساعت ۱۸:۲۹
      اگر ActionResult سفارشی هست، از context.HttpContext.Response.StatusCode = 403  و  بعد context.HttpContext.Response.End استفاده کنید. اگر ActionFilterAttribute سفارشی تعریف شده، از filterContext.Result = new Http403Result کمک بگیرید.
  • #
    ‫۹ سال و ۵ ماه قبل، یکشنبه ۲۳ فروردین ۱۳۹۴، ساعت ۲۲:۳۳
    بعد از انتقال از یک سرور به سرور دیگر ، خطایی در زمان نمایش کپچا ایجاد می‌شود :
    System.Security.Cryptography.CryptographicException: Object already exists at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)   at System.Security.Cryptography.Utils._CreateCSP(CspParameters param, Boolean randomKeyContainer, SafeProvHandle&amp; hProv)
    متن کامل خطا :
    captcha-error.txt

    تنها با دادن دسترسی کامل به Everyone برای مسیر زیر مشکل برطرف شد:
    C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
    در حالی که نه در سیستم لوکال و نه در سرور قبلی این مشکل وجود نداشت. سرور جدید 2012 هست.
    استفاده از Full Trust و یا دستور زیر هم کارساز نشد :
    aspnet_regiis -pa "SampleKeys" "NT AUTHORITY\NETWORK SERVICE"
  • #
    ‫۸ سال و ۵ ماه قبل، پنجشنبه ۹ اردیبهشت ۱۳۹۵، ساعت ۱۵:۵۱
    یک نکته تکمیلی:
    اگر ویو مدل شما به صورت ترکیبی است؛ مثلاً زمانیکه در یک ویو می‌خواهید دو فرم برای لاگین و ثبت‌نام داشته باشید:
    public class SignInAndSignUpViewModel
    {
            public SignInViewModel  SignIn { get; set; }
            public SignUpViewModel SignUp { get; set; } 
    }
    باید درون کلاس ValidateCaptchaAttribute تمامی رشته‌هایی که  CaptchaIntupText هستند:
    controllerBase.ValueProvider.GetValue("CaptchaInputText");
    controllerBase.ViewData.ModelState.AddModelError("CaptchaInputText", Error.....);
    را به صورت nested بنویسید:
    controllerBase.ValueProvider.GetValue("SignIn.CaptchaInputText");
    controllerBase.ViewData.ModelState.AddModelError("SignIn.CaptchaInputText", Error.....);
  • #
    ‫۷ سال و ۴ ماه قبل، چهارشنبه ۳ خرداد ۱۳۹۶، ساعت ۰۲:۱۴
    با تشکر فراوان از مقاله بسیار عالی
    سوالی که اینجا مطرحه راه کار برای استفاده از این ماژول در دو وب سایت که بر روی یک هاست بارگذاری شدند چیه ؟
    در واقع وقتی یکی از وب سایت‌ها فعال است برای وب سایت دوم این ماژول کار نمی‌کنه و پیام System.Security.Cryptography.CryptographicException: Object already exists دریافت می‌کنم.
    لطف می‌کنید راهنمایی کنید به چه طریقی مشکل حل می‌شه .

      • #
        ‫۷ سال و ۴ ماه قبل، چهارشنبه ۳ خرداد ۱۳۹۶، ساعت ۰۲:۳۸
        با تشکر؛ ممکنه دوستان به اروری مانند زیر بربخورن:
         CryptographicException was unhandled: System cannot find the specified file
        جهت حل این مشکل باید دسترسی IIS به KeyStore  رو فراهم کنن با دستور زیر :
         cspParams.Flags = CspProviderFlags.UseMachineKeyStore;