Discuz!NT官方社区

首页 » Discuz!NT开发与测试 » Discuz!NT整合 » DiscuzNT!与Membership整合,实现单点登录
strikerzhu - 2007-10-29 19:07:00
DiscuzNT是个很棒的开源论坛,但DiscuzNT使用了独特的会员帐户管理系统,如何将使用ASP.net Membership的网站与DsicuzNT整合在一起,是一个大家都很关心的问题。
DiscuzNT官方给出的单点登录方案,要在页面之间重定向,这一解决方案虽然在技术上简单易行,但在用户体验上并不理想。
我大略研究了DiscuzNT的源代码,给出了一个实现单点登录的整合方案,整合后,论坛与网站的注册、登录、退出都将一步完成,无需在页面之间进行多次重定向。
大家可以访问我的网站:
http://www.baibupai.com
体验一下。
本文是根据DiscuzNT2.0编写的,但我实现整合最初是在1.0平台下,后移至2.0平台对整合代码进行了少许调整,各位可以根据自己的情况实践。
实施平台说明:ASP.net 2.0,使用Membership做帐户管理,IIS6。
1、软件准备
由于DiscuzNT 2.0并没有提供源代码,因此需要对其进行反编译(有源代码的1.0版本不需要),我使用的反编译工具是Lutz Roeder's .net Relector,并安装了FileDisassembler插件。各位Google一下吧,很容易找到。
我在研究Discuz的过程中,使用了Firefox的Tamper Data插件,使用这个插件,各位可以轻松的了解到浏览器提交到服务器的数据结构,特别是Cookie的数据结构。因此我建议有兴趣研究DiscuzNT的Cookie结构的朋友可以装一个这个插件。
2、整合实现方案
实现DiscuzNT与原有网站用户帐户系统的整合,分为如下几步:
1、会员注册时同时完成论坛帐户注册
2、在单一的页面进行登录,登录后在网站和论坛中同时具有经过认证的身份标识。
3、在网站和论坛任意一个页面退出,将从网站和论坛同时退出。
4、网站的在线用户同时显示在论坛的在线列表中。
1、注册逻辑
  这个在DiscuzNT的文档中有详细说明,不多说:
2、登录逻辑
要做到单点登录,需要对.net的Form认证方式有比较深入的了解。Membership只是一种帐户管理系统,
与认证方式没有关系,.net的form认证方式是相同的。无论是.net的form认证,还是DiscuzNT的认证方式,
实际都是使用Cookie作为客户端身份的标识。因此如果能够实现:
在某个网页登录时,使客户端同时获得这几个网站的Cookie,即可简单的实现单点登录。
Cookie对象有一个属性名为Domain,这个属性标记着此Cookie来自哪个网站。
当浏览器访问某个网站时,会在内存或硬盘上的IE缓存中根据网站的域名查抄是否有这个网站的Cookie,
如果有,则把此Cookie作为HTTP Request的一部分发送给Web服务器,在Form认证方式中,
web服务器根据这个Cookie判断客户端的身份。
比如我的网站有两个域名,一个是www.baibupai.com,一个是bbs.baibupai.com(欢迎大家来逛逛,呵呵)
最基本的情况下,IE会保留两个Cookie,其Domain属性分别为www.baibupai.com,和bbs.baibupai.com。
当浏览器访问www.baibupai.com下面的某个页面时会把Domain是www.baibupai.com的Cookie发送给服务器,
服务器会根据这个Cookie的内容,判断客户端的身份。
此时由于两个Cookie的Domain不同,浏览器不会把www的Cookie发送给网站bbs,反之亦然。
感兴趣的朋友可以用firefox的Tamper Data捕获一个ASP.Net网站的Cookie看看其结构。
比如在asp.net的web.config中,通常配置了如下节点:
<authentication mode="Forms">
  <forms name="baibupai" loginUrl="login.aspx" protection="All" timeout="30" path="/">
  </forms>
</authentication>
forms的name属性为"baibupai",那么asp.net的身份认证Cookie会包含一个名为baibupai的变量,
该变量中包含一个加密的字符串(是否加密可在web.config中设置,通常应设置为加密),该字符串包含了客户端的身份信息。
再看DiscuzNT的Cookie结构:(使用Tamper data捕捉到的Cookie)
Cookie=dnttemplateid=0;
onlineusercount=4;
lastolupdate=1561536875;
dntadmin=;
dnt=userid=7&password=XXXXX%3d%3d&tpp=0&ppp=0&pmsound=1&invisible=0&referer=showtopic.aspx%3ftopicid%3d4192%26page%3d1&sigstatus=1&expires=-1&oldtopic=D4192&visitedforums=3&isframe=0
其中记录了客户端的uid等信息。
strikerzhu - 2007-10-29 19:08:00
因此要实现单点登录,必须做两件事:
1、把bbs和www两个网站关于身份认证的Cookie信息写到一个Cookie中;
2、设置这个Cookie的Domain属性,让IE在发送Request时无论访问上述哪个网站,都会把这个Cookie发送过去。
此时,无论浏览器访问哪个网站,都会把相同的Cookie发送给服务器,服务器就可以根据这个Cookie判断客户端的身份了。
首先在www.baibupai.com这个网站的web.config中,我配置了一个节点:
<appSettings>
<add key="domainName" value=".baibupai.com"/>
</appSettings>
在DiscuzNT的general.config中设置:
<CookieDomain>.baibupai.com</CookieDomain>
把DiscuzNT的dll文件拷贝的网站的bin目录,把Discuz的config文件夹和DNT.config文件也拷贝到www网站的根目录下。
把DiscuzNT的dll Import到工程中来
修改网站的登录代码:
private static void SetCookie(HttpCookie cookie, string domainName, bool IsPersistent, DateTime expires)
{
    if (cookie != null)
    {
        cookie.Domain = domainName;
        if (IsPersistent) cookie.Expires = expires;
        HttpContext.Current.Response.Cookies.Add(cookie);
    }
}
public static void Login(string name, string password, bool IsPersistent)
{
    if (Membership.ValidateUser(name, password))
    {
        FormsAuthentication.SetAuthCookie(name, IsPersistent);
        HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
        string domainName = ConfigurationManager.AppSettings["domainName"];
        DateTime expires;
        if (IsPersistent) expires = DateTime.Now.AddMinutes(525600);//一年
        else expires = DateTime.Now;
        SetCookie(cookie, domainName, IsPersistent, expires);
        //以上是常规的Membership的登录代码
        //以下是Discuz的登录代码
        //删除当前IP的历史登录错误
        LoginLogs.DeleteLoginLog(DNTRequest.GetIP());
        int uid = new MemberFcd().BbsGetUidByName(name);
        //根据积分重新刷新用户的积分用户组(如果不是积分用户组用户则不受影响)
        UserCredits.UpdateUserCredits(uid);
        //更新用户动作,olid当前用户在在线列表中的ID
        Discuz.Config.GeneralConfigInfo config = Discuz.Config.GeneralConfigs.GetConfig();
        Discuz.Entity.OnlineUserInfo oluserinfo = OnlineUsers.UpdateInfo(config.Passwordkey, config.Onlinetimeout);
        OnlineUsers.UpdateAction(oluserinfo.Olid, UserAction.Login.ActionID, 0, config.Onlinetimeout);
        //更新用户最后登录时间
        Users.UpdateUserLastvisit(uid, DNTRequest.GetIP());
        //写入用户的论坛登录cookie,由于在general.config中设置了CookieDomain,所以此Cookie的Domain也是.baibupai.com
        if (IsPersistent) ForumUtils.WriteUserCookie(uid, 525600, config.Passwordkey, 1, DNTRequest.GetInt("loginmode", -1));
        else ForumUtils.WriteUserCookie(uid, Utils.StrToInt(DNTRequest.GetString("expires"), -1), config.Passwordkey, 1, DNTRequest.GetInt("loginmode", -1));
        cookie = HttpContext.Current.Request.Cookies["dnt"];
        cookie = HttpContext.Current.Request.Cookies["dnttemplateid"];
        //bbs end
    }
}
3、退出出逻辑
同理当用户在网站上退出时,必须同时清除Asp.net的Cookie和Dnt的Cookie,在我的代码中,我设置所有相关Cookie变量的过期时间为DateTime.Now.AddDays(-1),这样浏览器会自动清除这些Cookie变量。
网站的Logout代码:
Session.Clear();
ForumUtils.ClearUserCookie();
Utils.WriteCookie("dnttemplateid", "", -999999);
Users.UpdateOnlineTime(Utils.StrToInt(ForumUtils.GetCookie("userid"), -1));
Discuz.Config.GeneralConfigInfo config = Discuz.Config.GeneralConfigs.GetConfig();
Discuz.Entity.OnlineUserInfo oluserinfo = Discuz.Forum.OnlineUsers.UpdateInfo(config.Passwordkey, config.Onlinetimeout);
OnlineUsers.DeleteRows(oluserinfo.Olid);
string domainName = ConfigurationManager.AppSettings["domainName"];
System.Web.HttpCookie c = Request.Cookies[FormsAuthentication.FormsCookieName];
if (c != null)
{
    c.Domain = domainName;
    c.Expires = DateTime.Now.AddDays(-1);
    Response.Cookies.Add(c);
}
c = Request.Cookies["dnt"];
if (c != null)
{
    c.Domain = domainName;
    c.Expires = DateTime.Now.AddDays(-1);
    Response.Cookies.Add(c);
}
c = Request.Cookies["dnttemplateid"];
if (c != null)
{
    c.Domain = domainName;
    c.Expires = DateTime.Now.AddDays(-1);
    Response.Cookies.Add(c);
}
同时必须修改DiscuzNT的Logout.aspx页面的代码,使其能够执行退出www网站的操作。
这里必须使用反编译工具,反编译得到Logout.aspx.cs源文件。
在:Utils.WriteCookie("dnttemplateid", "", -999999);之后插入:
string domainName = config.CookieDomain;
System.Web.HttpCookie c = System.Web.HttpContext.Current.Request.Cookies["baibupai"];
if (c != null)
{
    c.Domain = domainName;
    c.Expires = DateTime.Now.AddDays(-1);
    System.Web.HttpContext.Current.Response.Cookies.Add(c);
}
System.Web.HttpContext.Current.Response.Cookies.Add(c);
c = System.Web.HttpContext.Current.Request.Cookies["dnt"];
c.Domain = domainName;
c.Expires = DateTime.Now.AddDays(-1);
System.Web.HttpContext.Current.Response.Cookies.Add(c);
c = System.Web.HttpContext.Current.Request.Cookies["dnttemplateid"];
c.Domain = domainName;
c.Expires = DateTime.Now.AddDays(-1);
System.Web.HttpContext.Current.Response.Cookies.Add(c);
由于DiscuzNT的聚合页面:website.aspx的退出没有经过Logout.aspx的处理,必须对其退出进行特殊的处理:

Utils.WriteCookie("dnttemplateid", "", -999999);

base.SetUrl("website.aspx");
之间插入:
string domainName = config.CookieDomain;
System.Web.HttpCookie c = System.Web.HttpContext.Current.Request.Cookies["baibupai"];
if (c != null)
{
    c.Domain = domainName;
    c.Expires = DateTime.Now.AddDays(-1);
    System.Web.HttpContext.Current.Response.Cookies.Add(c);
}
System.Web.HttpContext.Current.Response.Cookies.Add(c);
c = System.Web.HttpContext.Current.Request.Cookies["dnt"];
c.Domain = domainName;
c.Expires = DateTime.Now.AddDays(-1);
System.Web.HttpContext.Current.Response.Cookies.Add(c);
c = System.Web.HttpContext.Current.Request.Cookies["dnttemplateid"];
c.Domain = domainName;
c.Expires = DateTime.Now.AddDays(-1);
System.Web.HttpContext.Current.Response.Cookies.Add(c);
此时,单点登录的主要工作已经完成了,在www.baibupai.com的login.aspx页面登录后,再访问bbs.baibupai.com,会显示自己已经登录。
同时,无论在哪个网站选择退出,都会同时退出另一个网站。
但还有两个缺点:
1、在DiscuzNT的分栏模式下,没法实现框架页面的登录状态与前台网站同步。
2、访问前台网站的用户,不会出现在论坛的在线会员中。
造成问题1的原因是,登录后,frame内的页面登录状态已经正确,但外面的frame页面没有经过刷新,仍然保持登录前的状态。
因此只要保证内页登录后,外部页面进行刷新即可。
首先要在discuzNT的页面中增加一个静态页面:refresh.htm。
页面中只须一句Javascript
<script language="javascript" type="text/javascript">window.location='focuslist.aspx';window.top.leftmenu.location='forumlist.aspx';</script>
由于是htm页面,所以必须放在DiscuzNT的根目录下,不要放在aspx目录下。
discuzNT在分栏模式下,会在Cookie中写一个"refurl"变量,记录是从哪个页面转过来的。这样在只要判断
cookie中有这个数据,即可判断登录请求来自分栏模式下的页面。
因此需要在前台登录页面中加入如下代码:
//正常情况下的登录重定向
if (Request.QueryString["ReturnUrl"] != null && Request.QueryString["ReturnUrl"] != "")
{
    string rntUrl = Request.QueryString["ReturnUrl"];
    int i = rntUrl.IndexOf("?ReturnUrl");
    if (i > 0) rntUrl = rntUrl.Remove(i, rntUrl.Length - i);
    Response.Redirect(rntUrl);
    //为了能跨站点登录,不能使用FormsAuthentication.RedirectFromLoginPage(name, false);
}
//在分栏模式下的登录重定向
else if (Request.Cookies["refurl"] != null && Request.Cookies["refurl"].Value != "")
{
    if (Request.Form["hdFrame"] == "1")
      Response.Redirect("http://bbs" + ConfigurationManager.AppSettings["domainName"] + "/refresh.htm");
    else Response.Redirect(Request.Cookies["refurl"].Value);
      Request.Cookies.Remove("refurl");
}
这样登录后,内页会重定向到refresh.htm,执行其中的javascript,外部的框架页也就得以刷新了。
问题2:需要在www网站页面的基类中或者每个页面中都会调用的部分代码中增加如下几行:
Discuz.Config.GeneralConfigInfo config = Discuz.Config.GeneralConfigs.GetConfig();
Discuz.Entity.OnlineUserInfo oluserinfo = Discuz.Forum.OnlineUsers.UpdateInfo(config.Passwordkey, config.Onlinetimeout);
Discuz.Forum.OnlineUsers.UpdateAction(oluserinfo.Olid, Discuz.Forum.UserAction.ShowForum.ActionID, 0, config.Onlinetimeout);
这样每个访问到这段代码的用户都会出现在DiscuzNT的在线列表中。
strikerzhu - 2007-10-29 19:11:00
OK了!:tuzki20:
剩下的工作,就是修改DiscuzNT的页面,把注册和登录的链接改到www上的相应页面就OK了。希望我的总结对大家有用。
ebottle - 2007-10-29 21:13:00
:tuzki4:  不错,顶你了
strikerzhu - 2007-10-30 8:36:00
补充一下,网站的Login页面需要加入两行代码才能保证分栏状态下的行为正确:
html一句:
<input type="hidden" id="hdFrame" name="hdFrame" value="0" />
javascript一句:
if (top.leftmenu) $("hdFrame").value=1;
netboy - 2007-10-30 15:45:00
to: strikerzhu
我按你的整合方法试了一下,在public static void Login(string name, string password, bool IsPersistent)中
执行到LoginLogs.DeleteLoginLog(DNTRequest.GetIP());一步时出现了以下错误:Discuz.Data.DatabaseProvider”的类型初始值设定项引发异常。这是怎么回事?
jackdraw - 2007-10-30 19:33:00
discuzcn不是开源的吗?还要非这么大力气去搞破解,小心人告你,说开源只是做做广告,喊一下就当真的开源
strikerzhu - 2007-10-30 22:04:00
这个错误应该是你的DNT配置有问题,DNT.config这个文件也要copy到前台网站的根目录下面。再有你的DNT.config配置对了么?
strikerzhu - 2007-10-30 22:08:00
这个我问过啦,官方说到2.0正式版发布的时候,会提供源代码。
开源对商业公司的好处也大得很啊,我还提了两个改进建议,也被采纳了。
开源之后,DiscuzNT等于免费获得了广大用户的测试、改进支持。
netboy - 2007-10-31 11:25:00


引用:
原帖由 strikerzhu 于 2007-10-30 22:04:00 发表
这个错误应该是你的DNT配置有问题,DNT.config这个文件也要copy到前台网站的根目录下面。再有你的DNT.config配置对了么?


DNT.config我已经Copy到网站的根目录下了,DNT.config配置应该没有问题,我的Discuznt2.0运行很正常,我直接从Discuznt2.0根目录Copy过来的。

下面是DNT.config的配置:
<?xml version="1.0"?>
<BaseConfigInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Dbconnectstring>Data Source=(local);User ID=sa;Password=123456;Initial Catalog=discuznt;Pooling=true</Dbconnectstring>
  <Tableprefix>dnt_</Tableprefix>
  <Forumpath>/</Forumpath>
  <Dbtype>SqlServer</Dbtype>
  <Founderuid>0</Founderuid>
</BaseConfigInfo>
strikerzhu - 2007-11-1 0:42:00
具体的异常信息贴一下吧。
“Discuz.Data.DatabaseProvider”的类型初始值设定项引发异常”
你是不是把全部的DiscuzNT的dll都Copy到你网站的bin下面了?
可能是没有找到SqlServer的那个Provider的dll把。
netboy - 2007-11-1 10:16:00
strikerzhu,你好!

具体的异常信息如下:

请检查DNT.config中Dbtype节点数据库类型是否正确,例如:SqlServer、Access、MySql
说明: 执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

异常详细信息: System.Exception: 请检查DNT.config中Dbtype节点数据库类型是否正确,例如:SqlServer、Access、MySql

源错误:


行 76:       
行 77:        //得到用户id
行 78:        int num = Users.GetUserID(username);             
行 79:
行 80:        LoginLogs.DeleteLoginLog(DNTRequest.GetIP());


源文件: d:\eCMS\Web\Login.aspx.cs    行: 78

堆栈跟踪:


[Exception: 请检查DNT.config中Dbtype节点数据库类型是否正确,例如:SqlServer、Access、MySql]
  Discuz.Data.DatabaseProvider.GetProvider() +159
  Discuz.Data.DatabaseProvider..cctor() +58

[TypeInitializationException: “Discuz.Data.DatabaseProvider”的类型初始值设定项引发异常。]
  Discuz.Data.DatabaseProvider.GetInstance() +0
  Discuz.Forum.Users.GetUserID(String username) +29
  Login.LoginBBS(String username, String pwd) in d:\eCMS\Web\Login.aspx.cs:78
  Login.ibLogin_Click(Object sender, ImageClickEventArgs e) in d:\eCMS\Web\Login.aspx.cs:36
  System.Web.UI.WebControls.ImageButton.OnClick(ImageClickEventArgs e) +105
  System.Web.UI.WebControls.ImageButton.RaisePostBackEvent(String eventArgument) +115
  System.Web.UI.WebControls.ImageButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +7
  System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +11
  System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +33
  System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5102

但我的DNT.config中Dbtype节点数据库类型是SqlServer,<Dbtype>SqlServer</Dbtype>,我用的是SqlServer2005应该没问题吧?
strikerzhu - 2007-11-1 23:20:00
确认以下问题:
1、DiscuzNT的dnt.config、config文件夹(包括所有文件)、bin目录下的所有文件是否已经全部都copy到了你自己网站的根目录下。
2、你自己的网站与DiscuzNT的.net版本是否一致?
3、DiscuzNT的dll是否已经引用到了你的工程中,你在编译网站的时候是否能正常通过。
4、Dnt.config的配置是否正确,比如连接串等等,下面是我的DNT.config文件,我使用的是Windows集成认证方式。
<?xml version="1.0"?>
<BaseConfigInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Dbconnectstring>Data Source=localhost;Initial Catalog=discuz;Integrated Security=True;Pooling=true</Dbconnectstring>
  <Tableprefix>dnt_</Tableprefix>
  <Forumpath>/</Forumpath>
  <Dbtype>SqlServer</Dbtype>
  <Founderuid>0</Founderuid>
</BaseConfigInfo>
hnzyhua - 2007-11-4 20:16:00
DZ2.0不算开源,只能说免费程序,源码封比都什么紧,程序里看不到一段CS代码,
爱迪生 - 2007-11-5 19:47:00
纯粹的顶一下
strikerzhu - 2007-11-8 9:45:00
不同意hnzyhua 。。。虽然没有公布设计文档,但源代码确实是公开的啊。
DiscuzNT并没有对dll做模糊化,如果故意不开源,那模糊化一下即使反编译出来,代码也没法看了。
支持Discuz保持开源~~~希望广大程序员为DiscuzNT添砖加瓦
67251026 - 2008-4-29 15:25:00
编译错误
说明: 在编译向该请求提供服务所需资源的过程中出现错误。请检查下列特定错误详细信息并适当地修改源代码。

编译器错误信息: CS0246: 找不到类型或命名空间名称“MemberFcd”(是否缺少 using 指令或程序集引用?)

源错误:



行 46:            //删除当前IP的历史登录错误
行 47:            LoginLogs.DeleteLoginLog(DNTRequest.GetIP());
行 48:            int uid = new MemberFcd().BbsGetUidByName(name);
行 49:            //根据积分重新刷新用户的积分用户组(如果不是积分用户组用户则不受影响)
行 50:            UserCredits.UpdateUserCredits(uid);
67251026 - 2008-4-29 15:26:00
MemberFcd 是引用哪个命名空间的?
1
查看完整版本: DiscuzNT!与Membership整合,实现单点登录