using System;

using System.Collections.Generic;

using System.Text;

using System.ComponentModel;

using System.Web.Security;


namespace BIT.Web.Accounts

{

    /// <summary>

    /// LoginCustomErrorEventArgs Ver 1.0

    /// 로그인 결과를 커스텀으로 보여주기 위해 만든 Args

    /// </summary>

    /// <remarks>06.07.05 - Prototype - 방실</remarks>

    public class LoginCustomErrorEventArgs : EventArgs

    {

        private Message errorCode;

        private string errorMessage;


        public LoginCustomErrorEventArgs(Message errorCode)

        {

            this.errorCode = errorCode;

            this.errorMessage = new AccountsMessage(this.errorCode).ToString();

        }


        public string ErrorMessage

        {

            get { return errorMessage; }

            set { errorMessage = value; }

        }

    }



    /// <summary>

    /// 커스텀 로그인 컨트롤 Ver 1.0

    /// 로그인 결과를 커스텀으로 보여 주기 위해 만든 컨트롤

    /// </summary>

    /// <remarks>06.07.05 - Prototype - 방실</remarks>

    public class ExLogin : System.Web.UI.WebControls.Login

    {

        private LoginCustomErrorEventArgs args;

        private object key = new object();

        public delegate void LoginCustomErrorHandler(LoginCustomErrorEventArgs e);


        [Category("Custom"), Description("사용자 정의 예외입니다..")]

        public event LoginCustomErrorHandler LoginCustomError

        {

            add

            {

                base.Events.AddHandler(key, value);

            }

            remove

            {

                base.Events.RemoveHandler(key, value);

            }

        }


        protected override void OnAuthenticate(System.Web.UI.WebControls.AuthenticateEventArgs e)

        {

            Message msg;

            e.Authenticated = ((CustomSqlMembershipProvider)(Membership.Provider)).CheckPassword(this.UserName, this.Password, out msg);

            if (!e.Authenticated)

            {

                AccountsMessage message = new AccountsMessage(msg);

                this.args = new LoginCustomErrorEventArgs(message.GetMessageSource());

            }

        }


        protected override void OnLoginError(EventArgs e)

        {

            base.OnLoginError(e);


            if (this.args != null)

            {

                this.OnLoginCustomError(this.args);

            }

        }


        protected void OnLoginCustomError(LoginCustomErrorEventArgs e)

        {

            LoginCustomErrorHandler handler = (LoginCustomErrorHandler)base.Events[key];

            if (handler != null)

            {

                handler(e);

            }

        }

    }

}


너무나 직관적입니다 ㅋ.

간단히 설명을 해보겠습니다..ㅡ.ㅡ;

먼저 핵심 클래스인 ExLogin를 보면 먼저 기존의 로그인 컨트롤을 상속 받았습니다..ㅋ

LoginCustomError 라는 이벤트를 만들고 이를 VS의 디자이너의 속성창에 노출 시키도록 특성을 설정 합니다.

이는 LoginCustomErrorHandler 라는 델리게이트 형입니다..

좋습니다..

그리고 발생되는 이벤트의 메시지를 처리 하기 위해 LoginCustomErrorEventArgs 클래스를 만듭니다..

이 클래스에는.. errorCode와 errorMessage라는 2개의 필드와 프라퍼티가 정의 되어 있습니다..그리고 생성자에는 errorCode를 받게끔 되어 있지요..


이제 간단한 준비는 끝났습니다.

로그인 컨트롤을 만들어 보죠.


로그인 컨트롤에서 주의 깊게 봐야 하는 이벤트는..

        protected override void OnAuthenticate(System.Web.UI.WebControls.AuthenticateEventArgs e)

이 이벤트입니다..

이 이벤트에서 인증 작업을 하는 겁니다..

자세한 부분은 MSDN에서 이 이벤트 부분을 살펴 보시고요..


e.Authenticated = ((CustomSqlMembershipProvider)(Membership.Provider)).CheckPassword(this.UserName, this.Password, out msg);


Authenticated라는 프라퍼티는 인증 여부를 리턴 받습니다..

이 프라퍼티에서 true가 설정이 되면 로그인 컨트롤은 폼인증이 성공적으로 되었다고 판단하고 SetCookie를 해줍니다..(물론 숨어 있는 코드입니다..^^;)

만약 이 프라퍼티에 false로 세팅이 되면 인증 실패를 의미 하며 OnLoginError 이벤트로 넘어 가게 됩니다..


만약 현재 멤버쉽프로바이더를 재정의 했다면 위와 같은 식으로 인증 하는 부분의 메소드를 호출 하면 될것이고요..

이 메소드는 public override bool ValidateUser(string userName, string password) 라는 멤버쉽 클래스에서 호출 되는 메소드입니다.

이 메소드를 재정의 해서 불러 오셔도 됩니다..

어떤 메소드를 호출하든지.. 호출 되는 메서드에서 해당 예외에 대한 플래그 값을 넘겨야 합니다..


예를 들면..

internal bool CheckPassword(string userName, string password, out Message msg)

        {

            string pass;

            int status;

            int failedPasswordAttemptCount;

            int failedPasswordAnswerAttemptCount;

            bool isApproved;

            DateTime lastLoginDate;

            msg = Message.OK;


            this.GetPasswordWithFormat(userName, true, out status, out pass, out failedPasswordAttemptCount, out failedPasswordAnswerAttemptCount, out isApproved, out lastLoginDate);

            switch (status)

            {

                case 1:

                    msg = Message.NotFoundUser;

                    return false;

                case 2:

                    msg = Message.DeletedUser;

                    return false;

                case 99:

                    msg = Message.ClosedAccount;

                    return false;

            }

            if (!isApproved)

            {

                msg = Message.NotApproved;

                return false;

            }


            bool flag1 = pass.Equals(this.EncryptPassword(password));

            if (!flag1)

            {

                msg = Message.InvalidPassword;

            }

            if ((flag1 && (failedPasswordAttemptCount == 0)) && (failedPasswordAnswerAttemptCount == 0))

            {

                return true;

            }


            SqlDB db = new SqlDB(this.connectionString);

            SqlParameter[] param = {

                new SqlParameter("@ReturnValue", SqlDbType.VarChar,16),

                new SqlParameter("@UserName", SqlDbType.VarChar, 64),

                new SqlParameter("@IsPasswordCorrect", SqlDbType.Bit),

                new SqlParameter("@UpdateLastLoginActivityDate", SqlDbType.Bit),

                new SqlParameter("@MaxInvalidPasswordAttempts", SqlDbType.Int),

                new SqlParameter("@PasswordAttemptWindow", SqlDbType.Int),

                new SqlParameter("@CurrentTime", SqlDbType.DateTime),

                new SqlParameter("@LastLoginDate", SqlDbType.DateTime)

            };

            param[0].Direction = ParameterDirection.ReturnValue;

            DateTime curDate = DateTime.Now;

            db.ExecuteNonQuery(false, CommandType.StoredProcedure, "Accounts_Membership_UpdateUserInfo", param, userName, flag1, true, this.MaxInvalidPasswordAttempts, this.PasswordAttemptWindow, curDate, flag1 ? curDate : lastLoginDate);

            return flag1;

        }


위 메소드는  CheckPassword(string userName, string password)라는 멤버쉽클래스의 메소드를 오버로딩 한 메소드입니다.

이 메소드 안에서..쿼리의 아웃풋으로 어떤 상태인지 리턴을 하게 됩니다.


기존 SqlMembership클래스의 CheckPassword메소드안에 설정되어 있는

if (status != 0)

            {

                return false;

            }

            if (!isApproved && failIfNotApproved)

            {

                return false;

            }

이 부분이

switch (status)

            {

                case 1:

                    msg = Message.NotFoundUser;

                    return false;

                case 2:

                    msg = Message.DeletedUser;

                    return false;

                case 99:

                    msg = Message.ClosedAccount;

                    return false;

            }

            if (!isApproved)

            {

                msg = Message.NotApproved;

                return false;

            }


            bool flag1 = pass.Equals(this.EncryptPassword(password));

            if (!flag1)

            {

                msg = Message.InvalidPassword;

            }

이 부분으로 바뀐것입니다..

이미 정의 되어 있는 부분은..단지 성공 실패 여부만 판단 하지만 새로 정의한 메소드에서는 실패하면 그 실패한 이유(에러코드)까지도 리턴 한다는게 차이점이 되겠습니다.


다시 원점으로 돌아 가서..

OnAuthenticate 이벤트롤 보면..

이런 과정으로 인증이 실패 되었으면 args에는 LoginCustomErrorEventArgs가 할당됩니다..


앞서 말했듯이

인증이 실패 하게 되면

그 다음으로는 OnLoginError 이벤트가 호출 됩니다..

이 이벤트에서는 인증 실패에 대한 어떤 메시지도 받을수 없습니다..ㅡ.ㅡ;

그래서

                this.OnLoginCustomError(this.args);

라고 이벤트를 한번 더 일으 킵니다..ㅋ


그럼 이제 의도한..OnLoginCustomError 이벤트가 발생됩니다.

이제 핸들러를 통해 e를 보내게 됩니다..


그러면..페이지에서는..

protected void Login1_LoginCustomError(BIT.Web.Accounts.LoginCustomErrorEventArgs e)

    {

        if (e.ErrorMessage != 0)

        {

            JScript.Alert(this.Page, e.ErrorMessage);            

        }

    }


이런 식으로 사용할 수 있게 됩니다..


마지막으로 메시지와 관련된 클래스를 보여 드리자면..

using System;

using System.Collections.Generic;

using System.Text;


namespace BIT.Web.Accounts

{

    /// <summary>

    /// AccountsMessage Ver 1.0

    /// </summary>

    /// <remarks>06.07.05 - Prototype - 방실</remarks>

    public class AccountsMessage

    {

        private Message msg;


        public AccountsMessage(Message msg)

        {

            this.msg = msg;

        }


        public Message GetMessageSource()

        {

            return this.msg;

        }


        public override string ToString()

        {

            switch (this.msg)

            {

                case Message.OK:

                    return "";

                case Message.UnKnownError:

                    return "알 수 없는 오류입니다.";

                case Message.NotFoundUser:

                    return "등록 되지 않은 사용자 입니다.";

                case Message.DeletedUser:

                    return "탈퇴한 사용자입니다.";

                case Message.InvalidPassword:

                    return "암호가 일치하지 않습니다.";

                case Message.ClosedAccount:

                    return "계정이 잠겨 있습니다.";

                case Message.NotApproved:

                    return "회원 가입이 허가되지 않았습니다.";

                default:

                    return "알 수 없는 오류입니다.";

            }

        }

    }


    public enum Message

    {

        OK,

        UnKnownError,

        NotFoundUser,

        DeletedUser,

        InvalidPassword,

        ClosedAccount,

        NotApproved

    }

}


뭐 이렇습니다..

+ Recent posts