问题的提出:解决网站原有的会员系统与ANF统一登录.注销.注册的问题,即将两个系统整合到一起
实现目的:一个帐号在两个系统下通用,只要帐号和密码一致就OK,但由于两个系统是分别存在的,所以两个会员系统只要任改一个各自的密码,便可分开来使用,互不关联
偶的解决方案(可能不是最好的)
(1)由于网站会员系统先于ANF运行,所以首先得转换网站会员资料到论坛里,第一步就得增加论坛Components/Enumerations/SecurityEnums.cs中UserPasswordFormat的枚举值(参考流水男孩的帖子http://bbs.hidotnet.com/5687/ShowPost.aspx ),做到密码一致,除非你的系统用的是明文密码 :)
(2)开始转换会员资料UpdateUsers.aspx:建议自己独立建个项目(该项目位于ANF的WEB目录下),并添加ANF相关的几个DLL的引用(主要是AspNetForums.dll和AspNetForums.Components.dll),实际上就是调用ANF会员注册的方法,详见Controls/User/CreateUser.cs,在转换时先把网站原有的会员信息select出来,主要是用户名.密码.昵称(没有的话用用户名替代).邮件信息,然后调用下面的CreateForumsUser逐个转换到论坛中:
using System.Configuration;
using System.Data.SqlClient;
using AspNetForums;
using AspNetForums.Components;
using AspNetForums.Enumerations;
#region 注册论坛用户
/// <summary>
/// 注册论坛用户
/// </summary>
/// <returns>返回bool值:注册是否成功</returns>
private bool CreateForumsUser(string UserName,string UserNick,string UserPass,string UserEmail)
{
bool bRegOK=false;
try
{
CreateUserStatus status = CreateUserStatus.UnknownFailure;
User user = new User();
user.Username = UserName;
user.Nickname = UserNick;
user.Email = UserEmail;
user.IPCreated = "000.000.000.000";
user.Password = UserPass;
user.AccountStatus = UserAccountStatus.Approved;
user.IsAnonymous = false;
if (user.Username == "Anonymous")
{
status = CreateUserStatus.DuplicateUsername;
}
else
{
status = CreateUser(user, false);
}
switch (status)
{
// Username already exists!
case CreateUserStatus.DuplicateUsername:
strMsg="论坛中已存在"+UserName;
break;
// Email already exists!
case CreateUserStatus.DuplicateEmailAddress:
strMsg="论坛中已存在"+UserEmail;
break;
// Unknown failure has occurred!
case CreateUserStatus.UnknownFailure:
strMsg="注册"+UserName+"时发生未知错误";
break;
// Username is disallowed
case CreateUserStatus.DisallowedUsername:
strMsg="论坛禁止"+UserName+"注册";
break;
// 昵称已存在
case CreateUserStatus.DuplicateNickname:
strMsg="论坛中已存在"+UserNick;
break;
// Everything went off fine, good
case CreateUserStatus.Created:
bRegOK=true;
break;
}
}
catch(Exception err)
{
info.Text+="<b><br><br>注册论坛用户时发生未知错误:"+err.ToString()+"<br><br></b>";
}
return bRegOK;
}
public static CreateUserStatus CreateUser(User user, bool sendEmail)
{
return Create(user,sendEmail,null,false);
}
// *********************************************************************
// CreateNewUser
//
/// <summary>
/// Creates a new user.
/// </summary>
/// <param name="user">A User object containing information about the user to create. Only the
/// Username and Email properties are used here.</param>
/// <returns></returns>
/// <remarks>This method chooses a random password for the user and emails the user his new Username/password.
/// From that point on, the user can configure their settings.</remarks>
///
// ********************************************************************/
public static CreateUserStatus Create(User user, bool sendEmail, string salt,bool isRequestFromPassport)
{
AccountActivation activation = Globals.GetSiteSettings().AccountActivation;
CreateUserStatus status;
string password=user.Password;
// Lucian: deprecated since it is not handled in CreateUser control
// and regEx validation on control's form.
// Make sure the username begins with an alpha character
//if (!Regex.IsMatch(user.Username, "^[A-Za-z].*"))
// return CreateUserStatus.InvalidFirstCharacter;
// Check if username is disallowed
if( DisallowedNames.NameIsDisallowed(user.Username) == true )
return CreateUserStatus.DisallowedUsername;
// Create Instance of the ForumsDataProvider
ForumsDataProvider dp = ForumsDataProvider.Instance();
/*密码当然不能转换的哈 // do we have a password?
if (user.Password == String.Empty)
{
password = Globals.CreateTemporaryPassword(14);
sendEmail = true;
}
else
{
password = user.Password;
}
// Encrypt the user's password
// only if EnablePasswordEncryption = true
//
if(salt == null)
{
user.Salt = Users.CreateSalt();
}
else
{
user.Salt = salt;
}*/
user.Salt="CCCCCCCCCCC="; //随便填一个,在数据库中以示区别 :)
//user.Password = Encrypt(Globals.GetSiteSettings().PasswordFormat, password, user.Salt);
user.PasswordFormat = UserPasswordFormat.GoonceShop; //使用你在上面创建的枚举值
user.ModerationLevel = Globals.GetSiteSettings().NewUserModerationLevel;
try
{
dp.CreateUpdateDeleteUser(user, DataProviderAction.Create, out status);
/*if(Globals.GetSiteSettings().EnablePassport && !isRequestFromPassport)
{
MemberAccount member = new MemberAccount(Globals.GetSiteSettings().PassportWebServiceUrl);
Itelite.Passport.WebServiceProxy.WebServiceUser wsUser = new Itelite.Passport.WebServiceProxy.WebServiceUser();
wsUser.UserName = Globals.GetSiteSettings().PassportLoginUserName;
wsUser.Password = Globals.GetSiteSettings().PassportLoginPassword;
member.WebServiceUserValue = wsUser;
Itelite.Passport.WebServiceProxy.Account account = new Itelite.Passport.WebServiceProxy.Account();
account.UserName = user.Username;
account.Password = user.Password;
account.Salt = user.Salt;
account.Email = user.Email;
member.Create(Globals.GetSiteSettings().PassportSiteGuid,account);
}*/
}
catch (ForumException e)
{
return e.CreateUserStatus;
}
// process the emails now
//
if(sendEmail == true)
{
// TDD HACK 7/19/2004
// we are about to send email to the user notifying them that their account was created, problem is
// when we create the user above we can't set the DateCreated property as this is set through the proc
// but the email needs to know the DateCreated property. So for now, we'll just set the date to the current
// datetime of the server. We don't care about the user local time at this point because the user hasn't
// logged in to set their user profile.
user.DateCreated = DateTime.Now;
user.LastLogin = DateTime.Now;
// based on the account type, we send different emails
//
switch (activation)
{
case AccountActivation.AdminApproval:
Emails.UserAccountPending (user);
break;
case AccountActivation.Email:
Emails.UserCreate(user, password);
break;
case AccountActivation.Automatic:
Emails.UserCreate(user, password);
break;
}
}
return CreateUserStatus.Created;
}
#endregion
(3)转换成功后,开始做统一登录:由于网站原有会员系统可能是ASP,也可能是.NET的,也有用session验证的,也有用cookie验证的,还要考虑到多虚拟目录的问题,这里就用数据库统一管理这些登录信息:
①先在网站原有会员系统数据库(SQL Server)中新添forums_login表,ID自动增长(bigint),forums_username(nvarchar),fourms_password(nvarchar),forums_logintime(datetime default:getdate() 记录登录时间,以查看出错记录),forums_seed(nvarchar 加密字符串) 该表用来存放登录某一个系统时的临时用户信息,以便在另一系统里自动登录(该表应随时为空,防止明文密码泄露)
②修改web.config文件(ASP系统请更改conn.asp数据库连接字符串):在网站原来会员系统web.config中添加fourms数据库连接信息,在ANF的web.config</configSections>结点后面新添以下信息
<appSettings>
<add key="YourSite" value="server=;uid=;pwd=;database=YourDb;connect timeout=5" />
<add key="forums" value="server=;database=forums;uid=;pwd=;connect timeout=5" />
<add key="site_login" value="/原网站会员登录目录(作转向用)/login.aspx" />
</appSettings>
③两种情况登录:
1)从原网站登录 在网站原会员系统登录程序登录成功的下一行添加:
//网站会员已经成功登录
//登陆论坛::开始-->主要方法为传递当前登陆信息到/forums/mylogin.aspx
cmd.CommandText="INSERT INTO forums_login(forums_username,forums_password,forums_seed) Values(@user_nick,@forums_pass,@forums_seed)"; //写用户登陆信息到数据库临时表
DateTime webNow=DateTime.Now;
string hashedSeed=FormsAuthentication.HashPasswordForStoringInConfigFile(webNow.ToString()+":"+webNow.Millisecond.ToString(),"SHA1");
cmd.Parameters.Add("@forums_pass",SqlDbType.NVarChar,50).Value=pwd.Text;
cmd.Parameters.Add("@forums_seed",SqlDbType.NVarChar,50).Value=hashedSeed;
cmd.ExecuteNonQuery();
con.Close();
Response.Redirect("/forums/MyLogin.aspx?User="+userid.Text+"&Seed="+hashedSeed+"&ReturnUrl=/YourSiteUrl");
//登陆论坛::结束-->转向论坛认证页面
2)从论坛登录 这个没法,必须得改ANF里Controls/Login.cs了,在SetLoginCookie(userToLogin.Username,autoLogin.SelectedValue);后面添加
//登陆网站原会员系统-->开始 主要方法为传递当前登陆信息到网站原会员登录页面
SqlConnection con=new SqlConnection(ConfigurationSettings.AppSettings["YourSite"]);
try
{
con.Open();
string strSql="INSERT INTO forums_login(forums_username,forums_password,forums_seed) Values(@shop_user,@shop_pass,@shop_seed)"; //写用户登陆信息到商城数据库临时表
SqlCommand cmd=new SqlCommand(strSql,con);
DateTime webNow=DateTime.Now;
string strSeed=webNow.ToString()+":"+webNow.Millisecond.ToString(); //Seed无安全性
strSeed=Users.Encrypt(Globals.GetSiteSettings().PasswordFormat, strSeed, Users.CreateSalt()); //增强了Seed的安全性 :)
cmd.Parameters.Add("@shop_user",SqlDbType.NVarChar,50).Value=username.Text;
cmd.Parameters.Add("@shop_pass",SqlDbType.NVarChar,50).Value=password.Text;
cmd.Parameters.Add("@shop_seed",SqlDbType.NVarChar,50).Value=strSeed;
cmd.ExecuteNonQuery();
redirectUrl=ConfigurationSettings.AppSettings["site_login"]+"?User="+username.Text+"&Seed="+strSeed+"&ReturnUrl="+redirectUrl;
}
catch
{
}
finally
{
con.Close();
}
//登陆商城会员系统-->结束
关于/forums/mylogin.aspx文件:是上面自己新建项目的转向登录页面,后台文件如下:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using AspNetForums;
using AspNetForums.Components;
using AspNetForums.Enumerations;
using System.Web.Security;
using System.Data.SqlClient;
using System.Configuration;
namespace Goonce.Shop
{
/// <summary>
/// MyLogin 的摘要说明。
/// </summary>
public class MyLogin : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
// 在此处放置用户代码以初始化页面
if(Request.QueryString["User"]!=null&&Request.QueryString["Seed"]!=null)
{
if(Request.QueryString["User"].Trim()!=string.Empty&&Request.QueryString["Seed"].Trim()!=string.Empty)
{
if(Request.QueryString["ReturnUrl"]!=null&&Request.QueryString["ReturnUrl"]!=string.Empty)
AutoLogin(Request.QueryString["ReturnUrl"]);
else
AutoLogin("/forums/default.aspx");
}
}
}
#region Web 窗体设计器生成的代码
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
private void AutoLogin(string url)
{
User userToLogin = new User();
SqlConnection con=new SqlConnection(ConfigurationSettings.AppSettings["YourSite"]);
try
{
con.Open();
SqlCommand cmd=new SqlCommand("SELECT forums_password AS password FROM forums_login WHERE forums_username=@User AND forums_seed=@Seed",con);
cmd.Parameters.Add("@User",SqlDbType.NVarChar,64).Value=Request.QueryString["User"];
cmd.Parameters.Add("@Seed",SqlDbType.NVarChar,64).Value=Request.QueryString["Seed"];
SqlDataReader reader=cmd.ExecuteReader();
if(reader.Read())
{
userToLogin.Password=reader["password"].ToString();
reader.Close();
//删除登录请求临时表中的信息
cmd.CommandText="DELETE FROM forums_login WHERE forums_username=@User AND forums_seed=@Seed";
cmd.ExecuteNonQuery();
userToLogin.Username=Request.QueryString["User"];
userToLogin.IPLastLogin = Globals.IPAddress;
LoginUserStatus loginStatus = Users.ValidUser(userToLogin);
if( loginStatus == LoginUserStatus.Success )
{
// Are we allowing login?
// TODO -- this could be better optimized
if (!Globals.GetSiteSettings().AllowLogin)
{
bool allowed = false;
int userid = Users.FindUserByUsername( userToLogin.Username ).UserID;
ArrayList roles = Roles.GetRoles(userid);
foreach (Role role in roles)
{
if (role.Name == "Site Administrators" || role.Name == "Global Administrators")
{
allowed = true;
break;
}
}
// Check the user is in the administrator role
if (!allowed)
{
//throw new ForumException(ForumExceptionType.UserLoginDisabled);
throw new Exception(); //如果用户不是管理员且被禁止登陆则抛出异常
}
}
// 设置cookie
FormsAuthentication.SetAuthCookie(userToLogin.Username,false);
}
}
else
{
reader.Close();
}
}
catch
{
//什么也不做,不提示出错信息
}
finally
{
con.Close();
Page.Response.Redirect(url, true);
}
}
}
}
同理,在你网站原来的会员系统登录转向页面里ConfigurationSettings.AppSettings["site_login"],用User和Seed来判断用登录的合法性,正确则授权登陆,代码就自己写了哈
(4) 统一注销 也是两种情况,也用转向注销的办法,从网站原会员系统注销页面上注销后转到/forums/MyLogout.aspx(自己的新建项目里)注销,代码主要就是以下两行
// log the user out
FormsAuthentication.SignOut();
// Nuke the roles cookie
Roles.SignOut();
从ANF上注销的话,将Logout.aspx前台文件改成<%Response.Redirect("/YourSite/logout.aspx");%>,和上面的一样,先注销网站原会员系统再转到/forums/MyLogout.aspx注销
(5) 统一注册:也是做到自己新建的项目里,放到/forums/register.aspx下,主要代码如下(参考Controls/User/CreateUser.cs和Components/Users.cs):先注册用户信息到网站原会员系统,再注册到论坛CreateForumsUser()
#region 注册论坛用户
/// <summary>
/// 注册论坛用户
/// </summary>
/// <returns>返回bool值:注册是否成功</returns>
private bool CreateForumsUser()
{
bool bRegOK=false;
try
{
CreateUserStatus status = CreateUserStatus.UnknownFailure;
User user = new User();
user.Username = userid.Text;
user.Nickname = usernick.Text;
user.Email = email.Text;
user.IPCreated = Globals.IPAddress;
user.Password = pwd.Text.Trim();
user.AccountStatus = UserAccountStatus.Approved;
user.IsAnonymous = false;
if (user.Username == "Anonymous")
{
status = CreateUserStatus.DuplicateUsername;
}
else
{
status = Users.Create(user, true);
}
switch (status)
{
// Username already exists!
case CreateUserStatus.DuplicateUsername:
strMsg="论坛中已存在该用户名!";
break;
// Email already exists!
case CreateUserStatus.DuplicateEmailAddress:
strMsg="论坛中已存在该电子邮箱!";
break;
// Unknown failure has occurred!
case CreateUserStatus.UnknownFailure:
strMsg="注册论坛用户时发生未知错误!";
break;
// Username is disallowed
case CreateUserStatus.DisallowedUsername:
strMsg="论坛中禁止该用户名注册!";
break;
// 昵称已存在
case CreateUserStatus.DuplicateNickname:
strMsg="论坛中已存在该昵称!";
break;
// Everything went off fine, good
case CreateUserStatus.Created:
strMsg+="与论坛";
bRegOK=true;
break;
}
}
catch
{
strMsg="注册论坛用户时发生未知错误,请联系管理员!";
}
return bRegOK;
}
#endregion
还有就是注册后应自动登陆的问题,由于该项目已在ANF下,所以只需
FormsAuthentication.SetAuthCookie(userid.Text, false); //自动登陆论坛
登录原会员系统还是参照上面转向登录的办法,另外别忘了将两个系统的注册页面转向本页面哈
后续:写到这里,是比较麻烦,要重编译ANF,要新建Web Application,还要改网站原会员系统相关页面,主要是因为各个网站原有会员系统各不一样,要做到统一管理的话,不可能一个程序通用的,我只能把大体思路写在这里,希望各位探讨,也希望更完美的程序出现 :)