ページ

Wednesday, February 2, 2011

Original Captcha System in an ASP.NET Web Pages on WebMatrix/IIS Express or IIS.

ReCaptcha is great to use Captcha system on a website easily, but if need to customize the sytem, then we'd better create another new one.
So I tried creating the system.


Captcha.cs
put dll file in bin folder after doing build the project on vs2010.

This requires the following resources in the project.
 Font as File (*** The font has to be used under the EULA(End User License Agreement) or something. anyway, you should ask the font vender or the copyright owner. ***)
 SESSION_KEY_NAME as string (ex. value = "CaptchaSessionKey")
 CAPTCHA_CODE_STRING as string (ex. value = "1234567890")

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.SessionState;


namespace CaptchaLibrary
{
    public sealed class Captcha : IDisposable
    {
        private readonly Random _random = new Random();
        private bool _disposed = false;

        private Bitmap Image { get; set; }
        private PrivateFontCollection PFC { set; get; }
        private IntPtr Ptr { set; get; }

        public Captcha(int width, int height)
        {
            string text = GenerateCaptchaCode(5);
            if (width > 0 && height > 0)
            {
                HttpContext.Current.Session[Properties.Resources.SESSION_KEY_NAME] = text;
                this.CreateBitmapImage(text, width, height);
            }
        }

        ~Captcha()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            lock (this)
            {
                if (!this._disposed)
                {
                    if (disposing)
                    {
                        if (this.Image != null)
                        {
                            this.Image.Dispose();
                            this.Image = null;
                        }
                        if (this.PFC != null)
                        {
                            this.PFC.Dispose();
                            this.PFC = null;
                        }
                        if (this.Ptr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(this.Ptr);
                            this.Ptr = IntPtr.Zero;
                        }
                    }
                }
                _disposed = true;
            }
        }

        public static bool Validate(string tagName)
        {
            bool isValid = false;
            ValidationErrorMessage = null;

            if (string.IsNullOrEmpty(tagName))
                throw new ArgumentNullException("tagName", "対象となるタグ名が指定されていません。");

            string paramValue = HttpContext.Current.Request.Form[tagName];
            if (string.IsNullOrEmpty(paramValue))
                ValidationErrorMessage = "認証コードを入力してください。";

            string sessionValue = HttpContext.Current.Session[Properties.Resources.SESSION_KEY_NAME].ToString();
            if (string.IsNullOrEmpty(sessionValue))
                ValidationErrorMessage = "タイムアウトです。再読み込みして、認証コードを生成しなおしてください。";

            if (string.IsNullOrEmpty(ValidationErrorMessage))
            {
                if (sessionValue.Equals(paramValue))
                    isValid = true;
                else
                    ValidationErrorMessage = "認証コードが違います。";
            }
            HttpContext.Current.Session.Remove(Properties.Resources.SESSION_KEY_NAME);
            return isValid;
        }

        public static string ValidationErrorMessage { private set; get; }
        public void OutputAsJpg()
        {
            HttpContext.Current.Response.Clear();
            HttpContext.Current.Response.ContentType = "image/jpg";
            HttpContext.Current.Response.Flush();
            if (Image != null)
                this.Image.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg);
            HttpContext.Current.Response.End();
        }

        private void CreateBitmapImage(string code, int width, int height)
        {
            this.Image = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            try
            {
                using (Graphics g = Graphics.FromImage(this.Image))
                {
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    Rectangle rect = new Rectangle(0, 0, width, height);

                    DrawBackground(g, rect);
                    DrawText(g, rect, code, true);
                    DrawRandomNoise(g, rect);
                    DrawRandomLines(g, rect, 2);
                }
            }
            catch (Exception)
            {
                this.Image = null;
            }
        }

        private void DrawBackground(Graphics g, Rectangle rect)
        {
            using (HatchBrush brush = new HatchBrush(HatchStyle.SmallConfetti, Color.LightGray, Color.WhiteSmoke))
                g.FillRectangle(brush, rect);
        }

        private void DrawText(Graphics g, Rectangle rect, string code, bool isWarp)
        {
            Font font = null;
            try
            {
                using (GraphicsPath path = new GraphicsPath())
                using (HatchBrush brush = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray))
                {
                    SizeF size;
                    float fontSize = rect.Height + 1;
                    FontFamily ff = GetFontFamily();
                    do
                    {
                        fontSize--;
                        font = new Font(ff, fontSize, FontStyle.Bold);
                        size = g.MeasureString(code, font);
                    } while (size.Width > rect.Width);
                    if (ff != null)
                    {
                        ff.Dispose();
                        ff = null;
                    }

                    StringFormat format = new StringFormat();
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;
                    path.AddString(code, font.FontFamily, (int)font.Style, font.Size, rect, format);

                    if (isWarp)
                    {
                        PointF[] points =
                            {
                                new PointF(this._random.Next(rect.Width) / 5F, this._random.Next(rect.Height) / 5F),
                                new PointF(rect.Width - this._random.Next(rect.Width) / 5F, this._random.Next(rect.Height) / 5F),
                                new PointF(this._random.Next(rect.Width) / 5F, rect.Height - this._random.Next(rect.Height) / 5F),
                                new PointF(rect.Width - this._random.Next(rect.Width) / 5F, rect.Height - this._random.Next(rect.Height) / 5F)
                            };
                        Matrix matrix = new Matrix();
                        matrix.Translate(0F, 0F);
                        path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);
                    }

                    g.FillPath(brush, path);
                }
            }
            finally
            {
                if (font != null)
                {
                    font.Dispose();
                    font = null;
                }
            }
        }

        private void DrawRandomNoise(Graphics g, Rectangle rect)
        {
            using (HatchBrush brush = new HatchBrush(HatchStyle.SmallConfetti, Color.LightGray, Color.DarkGray))
            {
                int max = Math.Max(rect.Width, rect.Height);
                for (int i = 0; i < (int)(rect.Width * rect.Height / 10F); i++)
                {
                    int x = this._random.Next(rect.Width);
                    int y = this._random.Next(rect.Height);
                    int w = this._random.Next(max / 50);
                    int h = this._random.Next(max / 50);
                    g.FillEllipse(brush, x, y, w, h);
                }
            }
        }

        private void DrawRandomLines(Graphics g, Rectangle rect, int lines)
        {
            using (HatchBrush brush = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray))
            using (Pen pen = new Pen(brush, 2))
            {
                for (int i = 0; i < lines; i++)
                {
                    float x = 0L;
                    float y = _random.Next(rect.Height);
                    PointF points = new PointF(x, y);
                    PointF pointe = new PointF(rect.Width - x, rect.Height - y);
                    g.DrawLine(pen, points, pointe);
                }
            }
        }

        private string GenerateCaptchaCode(int length)
        {
            string captchaCode = Properties.Resources.CAPTCHA_CODE_STRING;
            if (length < 0)
                length = 5;
            string code = "";
            for (int i = 0; i < length; i++)
                code = String.Concat(code, captchaCode.Substring(_random.Next(captchaCode.Length), 1));
            return code;
        }

        private FontFamily GetFontFamily()
        {
            try
            {
                byte[] bytes = Properties.Resources.Font;
                this.PFC = new PrivateFontCollection();
                this.Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(bytes[0]) * bytes.Length);
                Marshal.Copy(bytes, 0, this.Ptr, bytes.Length);
                this.PFC.AddMemoryFont(this.Ptr, bytes.Length);
                return this.PFC.Families[0];
            }
            catch (Exception)
            {
                return System.Drawing.FontFamily.GenericSerif;
            }
        }
    }

}


CaptchaImage.cshtml

@using CaptchaLibrary;
@{
    var captchaImg = new Captcha(240,50);
    captchaImg.OutputAsJpg();
    captchaImg.Dispose();
}


CaptchaControlPanel.cshtml
Put this file in App_Code folder

@helper Show(string tagName, string errorMsg ){
<div id="captcha">
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.4.4.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function(){
        $('#captcha_inputtag_reloadbutton').click(function(){
            $.ajax({
                type: 'GET',
                cache: false,
                success: function(msg, status){
                    $('#captcha_imgtag_captchaimage').attr('src', '@Href"~/CaptchaImage")?' + new Date().getTime());
                    return false;
                }
            });
        });
    });
</script>
<fieldset style="width: 320px;height: 120px;background-color: #ffffe0;position: relative; top: 0px;left: 0px;">
    <legend title="認証コード(CAPTCHA)">画像認証(CAPTCHA)</legend>
    <div align="left" style="width:320px;height: 60px;background-color: transparent;position: relative;top: -15px;left: 5px;">
        <img id="captcha_imgtag_captchaimage" src="@Href("~/CaptchaImage")" alt="CAPTCHA IMAGE" title="認証用画像(CAPTCHA IMAGE)" />
        <input type="image" src="[Image path for reload button]" id="captcha_inputtag_reloadbutton" alt="再読込ボタン" title="再読込ボタン" onclick="return false;" style="width:50px;height:50px;position:relative;top:auto;left:5pt;" /><br />
        <strong>上の画像に表示されているコードを入力してください。</strong>
    </div>
    <div align="left" style="width: 320px;height: 60px;background-color: transparent;position: relative;top: 0px;left: 5px;">
        <input type="text" name="@tagName" title="画像認証コード(CAPTCHA Code)入力ボックス" @if(!errorMsg.IsEmpty()){<text>class="error-field"</text>} />
        @if (!errorMsg.IsEmpty()) {
        <label for="@tagName" class="validation-error" style="position: relative;top: 0px;left: 0px;">
            @errorMsg
        </label>
        }
    </div>
</fieldset>
</div>
}

Register.cshtml

@using CaptchaLibrary;
@{
    if(WebSecurity.IsAuthenticated)
    {
        Response.Redirect(Href("~/"));
    }
    Response.CacheControl = "no-cache";
    
    Layout = "~/_SiteLayout.cshtml";
    Page.Title = "アカウントの登録";
    Page.Description = "新規登録フォーム";
    var email = "";
    var password = "";
    var confirmPassword = "";
    
    var isValid = true;
    var emailErrorMessage = "";
    var passwordErrorMessage = "";
    var confirmPasswordMessage = "";
    var accountCreationErrorMessage = "";
    var captchaTextErrorMessage = "";
    
    if (IsPost) {
        email = Request.Form["email"];
        password = Request.Form["password"];
        confirmPassword = Request.Form["confirmPassword"];
        
        if (email.IsEmpty()) {
            emailErrorMessage = "電子メール アドレスを入力してください。";
            isValid = false;
        }
        if (password.IsEmpty()) {
            passwordErrorMessage = "パスワードを空白にすることはできません。";
            isValid = false;
        }
        if (password != confirmPassword) {
            confirmPasswordMessage = "新しいパスワードと確認のパスワードが一致しません。";
            isValid = false;
        }
        if(!Captcha.Validate("captchaText"))
        {
            captchaTextErrorMessage = Captcha.ValidationErrorMessage;
            isValid = false;
        }
        if (isValid) {
            var db = Database.Open([db name(w/o ".sdf"]);
            var user = db.QuerySingle("SELECT Email FROM UserProfile WHERE LOWER(Email) = LOWER(@0)", email);
            if (user == null) {
                db.Execute("INSERT INTO UserProfile (Email) VALUES (@0)", email);
                try {
                    bool requireEmailConfirmation = !WebMail.SmtpServer.IsEmpty();
                    var token = WebSecurity.CreateAccount(email, password, requireEmailConfirmation);
                    if (requireEmailConfirmation) {
                        var hostUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
                        var confirmationUrl = hostUrl + VirtualPathUtility.ToAbsolute("~/Account/Confirm?confirmationCode=" + HttpUtility.UrlEncode(token));
                        WebMail.Send(
                            to: email, 
                            subject: "アカウントを確認してください", 
                            body: "確認コード:  " + token + "。<a href=\"" + confirmationUrl + "\">" + confirmationUrl + "</a> にアクセスしてアカウントを有効にしてください。"
                        ); 
                    }
                    if (requireEmailConfirmation) {
                        Response.Redirect("~/Account/Thanks");
                    } else {
                        WebSecurity.Login(email, password);
                        Response.Redirect("~/");
                    }
                } catch (System.Web.Security.MembershipCreateUserException e) {
                    isValid = false;
                    accountCreationErrorMessage = e.ToString();
                }
            } else {
                isValid = false;
                accountCreationErrorMessage = "電子メール アドレスは既に使用中です。";
            }
        }    
    }
}
<p>
   新しいアカウントを作成するには、以下のフォームを使用してください。 
</p>
@if (!isValid) {
   <p class="message error">
    @if (accountCreationErrorMessage.IsEmpty()) {
        @:エラーを修正し、再試行してください。
    } else {
        @accountCreationErrorMessage
    }
   </p>
}
<form method="post" action="">
    <fieldset>
        <legend>申し込みフォーム</legend>
        <ol>
            <li class="email">
                <label for="email">電子メール:</label>
                <input type="text" id="email" name="email" title="Email address" value="@email" @if(!emailErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!emailErrorMessage.IsEmpty()) {
                    <label for="email" class="validation-error">@emailErrorMessage</label>
                }
            </li>
            <li class="password">
                <label for="password">パスワード:</label>
                <input type="password" id="password" name="password" title="パスワード" @if(!passwordErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!passwordErrorMessage.IsEmpty()) {
                    <label for="password" class="validation-error">@passwordErrorMessage</label>
                }
            </li>
            <li class="confirm-password">
                <label for="confirmPassword">パスワードの確認入力:</label>
                <input type="password" id="confirmPassword" name="confirmPassword" title="パスワードの確認入力" @if(!confirmPasswordMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!confirmPasswordMessage.IsEmpty()) {
                    <label for="confirmPassword" class="validation-error">@confirmPasswordMessage</label>
                }
            </li>
            <li class="recaptcha">
                <!-- CAPTCHA Control Panel -->
                <p>
                    @CaptchaControlPanel.Show("captchaText", @captchaTextErrorMessage)
                </p>
                <!-- CAPTCHA Control Panel -->
            </li>
        </ol>
        <p class="form-actions">
            <input type="submit" value="登録" title="登録" />
        </p>
    </fieldset>
</form>


Register.cshtml




Error Message 1











Error Message 2


No comments:

Post a Comment