博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

TaskVision用户验证

Posted on 2008-10-07 15:56  a-peng  阅读(2875)  评论(6编辑  收藏  举报

前两篇介绍了:TaskVision自动更新与本地化。这两个功能比较独立,通常我们可以在软件开发完毕后加上,所以从该篇开始,小菜会将这两部份功能去掉,直到最后才会加上。

(一)、引言
TaskVision用户验证采用加密过后的身份验证票(FormsAuthenticationTicket+FormsAuthentication),客户端可通过注册表保存使用DataProtection加密过后的密码。

(二)、效果
正确帐号与密码如下:
帐号:jdoe
密码:welcome

输入帐号与密码:aa,bb
 image
注意了:小菜演示时,密码文本框暂时没有使用PasswordChar属性,为了让大家可以看清输入的密码。

登录提示:
image

停止IIS站点提示:
image 

输入帐号与密码:jdoe,welcome,选择记住密码,则将帐号与密码保存到注册表,下次就不用输写。
image

登录成功,在工具栏的右边显示用户全名。
image

小菜自定义数据验证控件来增强用户体验:(小菜自我感觉良好,代码很清晰,扩展性也不赖。)
 image
你可能会觉得,噢,不过是使用了HelpProvider组件而已,是使用了HelpProvider,但不是而已。(之后会详细介绍自定义数据验证控件)

代码下载:https://files.cnblogs.com/a-peng/SmartClient_Chapter03.rar

(三)、分析

1、数据库部份:

从该篇开始,我们将涉及到TaskVision数据库了,小菜会陆续给出相关表与存储过程。
该篇主要涉及:Users表(用户表)

列名 数据类型 长度 允许空 描述
UserID int 4   主键,自动编号
UserName varchar 16   用户名称
UserPassword varchar 16   用户密码
UserFullName varchar 50   用户全名
UserEmail varchar 50   用户邮箱
IsAccountLocked bit 1   帐号是否锁定,默认值(0)
IsAdministrator bit 1   用户是否具有管理权限,默认值(0)
DateCreated datetime 8   用户创建时间,默认值(getdate())

数据库中已有内容:

image 
存储过程:
AuthenticateUser用户验证:

CREATE PROCEDURE AuthenticateUser
(
  
@UserName varchar(16),
  
@Password varchar(16)
)
AS  
SELECT  UserID
FROM    Users
WHERE   (UserName = @UserName AND cast(UserPassword as varbinary= cast(@Password as varbinaryAND IsAccountLocked = 0)

使用cast(UserPassword as varbinary)表示将密码使用二进制进行比较,可以区分大小写,如welcome != Welcome

GetUserInfo取用户信息

CREATE PROCEDURE [GetUserInfo]
(
    
@UserID int
)
AS
SELECT  UserID, UserName, UserFullName, UserEmail, IsAdministrator, IsAccountLocked
FROM    Users
WHERE   (UserID = @UserID)


2、Web服务部份
我们创建了Web站点TaskVisionWS,添加了用于用户验证的Web服务:AuthService.asmx。

数据库相关操作使用:SqlHelper。
该SqlHelper来自Enterprise Library2.0(企业库2.0)中的Microsoft Data Access Application Block(数据访问应用程序块)

企业库中的SqlHelper比PetShop中的SqlHelper功能更强些,连参数都可以不会写。很好很强大。
大家有时间可以看看其源码,不会太难。

题外话:小菜经常逛TerryLee(李会军)的博客,TerryLee写过一个系列:Enterprise Library系列文章回顾与总结
之前一直被Enterprise Library的名字吓到,不敢看,呵,希望大家不要和我一样,勇敢点,你会发现没什么的。
Enterprise Library2.0为微软开源的.Net企业库,包含有:数据访问程序块,日志应用程序块,异常应用程序块,缓存应用程序块,安全应用程序块,加密应用程序块。
可以从这里下载到:http://msdn.microsoft.com/en-us/library/aa480453.aspx 而且都带有例子QuickStartSamples

AuthService.asmx源码:

public class AuthService : System.Web.Services.WebService
{
    
private const string m_connectionString = "data source=localhost;initial catalog=TaskVision;integrated security=SSPI;persist security info=False";
    
private SqlConnection m_conn;

    
public AuthService()
    
{
        m_conn 
= new SqlConnection(m_connectionString);
    }


    [WebMethod]
    
// return null:未通过验证,反之通过
    public string GetAuthorizationTicket(string userName, string userPassword)
    
{
        
string userID = null;

        
try
        
{
            userID 
= SqlHelper.ExecuteScalar(m_conn, "AuthenticateUser", userName, userPassword).ToString();
        }

        
catch
        
{
        }

        
finally
        
{
            
if (m_conn.State != ConnectionState.Closed)
                m_conn.Close();
        }


        
if (userID == null)
            
return null;

        
// 创建身份验证票证有效时间1分钟并加密
        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(userID, false1);
        
string encryptedTicket = FormsAuthentication.Encrypt(ticket);

        
// 缓存身份验证票证2分钟
        Context.Cache.Insert(encryptedTicket, userID, null, DateTime.Now.AddMinutes(2), TimeSpan.Zero);

        
return encryptedTicket;
    }


    [WebMethod()]
    
// return null:未通过验证,反之通过
    public UserInformation GetUserInfo(string ticket)
    
{
        
if (!IsTicketValid(ticket))
            
return null;

        
// 解密身份验证票证,取出userID
        int userID = int.Parse(FormsAuthentication.Decrypt(ticket).Name);

        DataTable tbl 
= null;

        
try
        
{
            tbl 
= SqlHelper.ExecuteDataset(m_conn, "GetUserInfo", userID).Tables[0];
        }

        
catch
        
{ }
        
finally
        
{
            
if (m_conn.State != ConnectionState.Closed)
                m_conn.Close();
        }


        
if (tbl == null || tbl.Rows.Count == 0)
            
return null;

        UserInformation userInfo 
= new UserInformation();

        DataRow dr 
= tbl.Rows[0];

        userInfo.UserID 
= userID;
        userInfo.UserName 
= (string)dr["UserName"];
        userInfo.UserFullName 
= (string)dr["UserFullName"];
        userInfo.UserEmail 
= (string)dr["UserEmail"];
        userInfo.IsAdministrator 
= (bool)dr["IsAdministrator"];
        userInfo.IsAccountLocked 
= (bool)dr["IsAccountLocked"];

        
return userInfo;
    }


    
// return false:未通过验证的票证,反之通过
    private bool IsTicketValid(string ticket)
    
{
        
if (ticket == null || Context.Cache[ticket] == null)
        
{
            
return false;
        }

        
else
        
{
            
int userID = int.Parse(FormsAuthentication.Decrypt(ticket).Name);

            DataTable tbl 
= null;

            
try
            
{
                tbl 
= SqlHelper.ExecuteDataset(m_conn, "GetUserInfo", userID).Tables[0];
            }

            
catch
            
{
            }

            
finally
            
{
                
if (m_conn.State != ConnectionState.Closed)
                    m_conn.Close();
            }


            
if (tbl == null || tbl.Rows.Count == 0)
                
return false;

            UserInformation userInfo 
= new UserInformation();

            DataRow dr 
= tbl.Rows[0];
            userInfo.IsAccountLocked 
= (bool)dr["IsAccountLocked"];

            
if (userInfo.IsAccountLocked) // 用户被锁定
                return false;
            
else
                
return true;
        }

    }

}

从userID = SqlHelper.ExecuteScalar(m_conn, "AuthenticateUser", userName, userPassword).ToString(); 我们可以看到使用企业库中的SqlHelper执行存储过程,可以不用写参数。感兴趣的朋友可以看看其源码噢。

3、本地应用程序部份
本地应用程序引用Web服务AuthService.asmx,名称为AuthWS

我们创建DataLayer类来封装对Web服务的调用。

using System;
using TaskVision.AuthWS;

namespace TaskVision
{
    
public class DataLayer
    
{
        
private string m_ticket;
        
private AuthService m_authService;

        
private UserInformation m_currentUser = new UserInformation();

        
public DataLayer()
        
{
            m_authService 
= new AuthService();
            
//InitAuthServiceProxy(m_authService);
        }


        
public UserInformation CurrentUser
        
{
            
get
            
{
                
return m_currentUser;
            }

        }


        
// 用户登录
        public WebServicesExceptionType Login(string userName, string userPassword)
        
{
            m_currentUser.UserName 
= userName;
            m_currentUser.UserPassword 
= userPassword;

            WebServicesExceptionType ticketExceptionType 
= GetAuthorizationTicket();
            
            
if (ticketExceptionType == WebServicesExceptionType.None)
            
{
                UserInformation newUser;

                
try
                
{
                    newUser 
= m_authService.GetUserInfo(m_ticket);
                }

                
catch (Exception ex)
                
{
                    
return HandleException(ex);
                }


                
if (newUser == null)
                    
return WebServicesExceptionType.AuthenticationException;

                m_currentUser.UserID 
= newUser.UserID;
                m_currentUser.UserName 
= newUser.UserName;
                m_currentUser.UserFullName 
= newUser.UserFullName;
                m_currentUser.UserEmail 
= newUser.UserEmail;
                m_currentUser.IsAdministrator 
= newUser.IsAdministrator;
                m_currentUser.IsAccountLocked 
= newUser.IsAccountLocked;

                
return WebServicesExceptionType.None;
            }

            
else
            
{
                
return ticketExceptionType;
            }

        }


        
// 取得用户验证票证
        private WebServicesExceptionType GetAuthorizationTicket()
        
{
            
try
            
{
                m_ticket 
= m_authService.GetAuthorizationTicket(m_currentUser.UserName, m_currentUser.UserPassword);
            }

            
catch (Exception ex)
            
{
                m_ticket 
= null;
                
return HandleException(ex);
            }


            
if (m_ticket == null)
            
{
                
return WebServicesExceptionType.AuthenticationException;
            }


            
return WebServicesExceptionType.None;
        }


        
// 处理异常
        private WebServicesExceptionType HandleException(Exception ex)
        
{
            
if (ex is System.Net.WebException)
                
return WebServicesExceptionType.WebException;
            
else if (ex is System.Web.Services.Protocols.SoapException)
                
return WebServicesExceptionType.SoapException;
            
else
                
return WebServicesExceptionType.Exception;
        }


        
// 初始化用户验证服务
        
//private void InitAuthServiceProxy(AuthService service)
        
//{
        
//    string urlSetting = System.Configuration.ConfigurationManager.AppSettings["AuthServiceUrl"];

        
//    if (urlSetting != null)
        
//    {
        
//        service.Url = urlSetting;
        
//    }
        
//    else
        
//    {
        
//        service.Url = "http://localhost/TaskVisionWS/AuthService.asmx";
        
//    }
        
//}
    }

}

你会发现,小菜注释了一部份代码,为何呢?
Web服务的地址会改变,如果我们将其Url地址固定在程序中,会带来很多不便,所以我们通常会将其放在配置文件中如app.Config,然后读取。所以也就有了上面的InitAuthServiceProxy(AuthService service)来初始化该Web服务。然而在.Net2.0中,当我们引用Web服务,VS2005就会自动将引用地址写入app.Config配置文件中,所以无需我们做这部份的内容了。配置文件内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
<configSections>
        
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            
<section name="TaskVision.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        
</sectionGroup>
    
</configSections>
    
<applicationSettings>
        
<TaskVision.Properties.Settings>
            
<setting name="TaskVision_AuthWS_AuthService" serializeAs="String">
                
<value>http://localhost/TaskVisionWS/AuthService.asmx</value>
            
</setting>
        
</TaskVision.Properties.Settings>
    
</applicationSettings>
</configuration>

如果你对VS2005自定生成的配置文件代码很不满意的话,那你可以使用如下配置文件,然后把代码的注释部份去掉,小菜已经为你准备好了。 :)

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
<appSettings>
        
<add key="AuthService" value="http://localhost/TaskVisionWS/AuthService.asmx"/>
    
</appSettings>
</configuration>

接下来看看用户登录窗体的代码:

using System;
using System.Windows.Forms;

using Microsoft.Win32;

namespace TaskVision
{
    
public partial class LoginForm : Form
    
{
        
private const string m_registryKey = @"Software\Microsoft\TaskVision";
        
private DataLayer m_dataLayer;

        
public LoginForm(DataLayer dataLayer)
        
{
            InitializeComponent();

            m_dataLayer 
= dataLayer;
        }


        
private void LoginForm_Load(object sender, EventArgs e)
        
{
            lblVersion.Text 
= "Version " + Application.ProductVersion;

            
string userName = string.Empty;
            
string password = string.Empty;

            
try
            
{
                RegistryKey regKey 
= Registry.CurrentUser.OpenSubKey(m_registryKey);

                
if (regKey != null && regKey.GetValue("UserName"!= null && regKey.GetValue("Password"!= null)
                
{
                    userName 
= regKey.GetValue("UserName").ToString();
                    password 
= DataProtection.UnprotectData(regKey.GetValue("Password").ToString());
                    regKey.Close();
                }

            }

            
catch
            
{
            }


            
if (userName != string.Empty && password != string.Empty)
            
{
                txtUserName.Text 
= userName;
                txtPassword.Text 
= password;
                cbRemember.Checked 
= true;
            }


            txtUserName.Focus();
        }


        
private void btnOk_Click(object sender, EventArgs e)
        
{
            
this.DialogResult = DialogResult.None;
            
if (fvLoginForm.IsValid)
            
{
                
this.Cursor = Cursors.WaitCursor;
                WebServicesExceptionType loginExceptionType 
= m_dataLayer.Login(txtUserName.Text, txtPassword.Text);
                
base.Cursor = Cursors.Arrow;

                
if (loginExceptionType == WebServicesExceptionType.None) // 登录成功    
                  {
                    
try
                    
{
                        RegistryKey regKey 
= Registry.CurrentUser.CreateSubKey(m_registryKey);
                        
if (cbRemember.Checked)
                        
{
                            regKey.SetValue(
"UserName", txtUserName.Text);
                            regKey.SetValue(
"Password", DataProtection.ProtectData(txtPassword.Text, "TaskVisionPassword"));
                        }

                        
else
                        
{
                            regKey.DeleteValue(
"UserName"false);
                            regKey.DeleteValue(
"Password"false);
                        }

                        regKey.Close();
                    }

                    
catch
                    
{
                    }


                    
this.DialogResult = DialogResult.OK;
                    
this.Close();
                }

                
else
                
{
                    txtUserName.Focus();

                    
if (loginExceptionType == WebServicesExceptionType.WebException)
                        MessageBox.Show(
"连接服务器超时.""错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    
else if (loginExceptionType == WebServicesExceptionType.SoapException)
                        MessageBox.Show(
"服务器出现异常.""错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    
else if (loginExceptionType == WebServicesExceptionType.AuthenticationException)
                        MessageBox.Show(
"帐号或密码错误.""错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    
else
                        MessageBox.Show(
"未知错误.""错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

            }

        }


        
private void btnCancel_Click(object sender, EventArgs e)
        
{

        }

    }

}

你会看到在btnOk_Click中有这句代码:if (fvLoginForm.IsValid) { } fvLoginForm为自定义数据验证控件,具体的分析小菜下篇会写,感兴趣的可以先查看小菜的源码。这里表示当帐号与密码两个文本框输入不为空时返回true。

主窗体:

public partial class MainForm : Form
{
    
private bool m_isWorkOnline = true// 默认在线工作

    
private DataLayer m_dataLayer;

    
public MainForm()
    
{
        InitializeComponent();
    }


    
private void MainForm_Load(object sender, EventArgs e)
    
{
        m_dataLayer 
= new DataLayer();

        
if (m_isWorkOnline)
        
{
            
if (DisplayLoginForm() == DialogResult.Cancel)
            
{
                
this.Close();
                
return;
            }


            tbUserName.Text 
= m_dataLayer.CurrentUser.UserFullName;
        }

    }


    
private DialogResult DisplayLoginForm()
    
{
        LoginForm loginForm 
= new LoginForm(m_dataLayer);
        DialogResult loginFormResult 
= loginForm.ShowDialog();

        
return loginFormResult;
    }

}

 

*************************************************************************
作者:a-peng
出处:
http://a-peng.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出
原文连接,否则保留追究法律责任的权利。
*************************************************************************