Step1帐户登录系统

0.整体思路

我一直想做一个帐户登录系统,到今天,终于做出了一个雏形,非常高兴,因此,我会在下面的几篇文章对这个系统进行详细的介绍,这是第一篇,介绍一下整体思路,到后面,基本上就是以代码为主了,先看个截图:

Step1帐户登录系统

这个系统起源于单点登录系统(实际上它本身也是一个单点登录系统),这个技术现在在互联网上使用的十分广泛了,毕竟,无论是大的还是小的网站,是否有多个域名,都可太可能为每个栏目来设计一个单独的登录系统,等不可能让用户在每个栏目都去输入帐号密码,下面我粗略的图解一下单点登录系统(这篇文章之中提到的登录都应该兼容跨域名之间的接口的,如果在同一个域名内,将会容易得多)

以上这个图只是一个登录流程的顺序图,按照这个图,我们一步一步看一下:
1.用户要求进行某一操作;
2.Web栏目通过自身的Cookie或Session判断用户是否已经登录,如果已经登录,则直接处理,否则,将用户导向到登录系统,并附带上参数通知登录系统在登录完成之后返回到哪一个页面;
3.登录系统通过Cookie或Session判断用户是否已经登录,如果已经登录,则直接将用户跳转回栏目,并附带用户的帐户信息,如果没有登录,则提示用户登录;
4.用户输入帐户密码;
5.登录系统记录判断用户登录是否正常,然后先设置自己的Cookie或Session,再使用用户的帐户信息跳转到对应的栏目;
6.栏目收到跳转回来的请求之后,先验证请求,然后设置自己的Cookie,再根据用户的帐户信息向用户返回内容。
本来这些文字描述都可以直接直观的画在图上的,不过我的发现机器上没有工具,连Word都没有,只好随便用画图画了一幅,不过因为大家对单点登录应该都很了解,因此,肯定会跳过不看,也就无所谓了。

我并不是要做一个单点登录系统,我主要是想,现在的网站,无论大小,都有一个注册、登录什么的,对用户造成很大的困扰,用户不可能去记那么多帐号密码的,对网站来讲,维护用户的数据也是一个很难的问题,因此,如果网站能够相互公用用户信息,让用户的一个密码在多个网站都可以登录,这是一个皆大欢喜的事情。这个思路,实际上就是传说中的OpenId,我之所以没有去专门研究OpenId,是因为现在国内知道的人很少,而且OpenId还是有一定的局限性,我仔细的研究了各个大的网站提供的对外接口(国内的很少,基本都是国外的),决定写一个基于多种网站用户来源的单点登录系统,这就是刚才看到的Step1帐户登录系统.

其实,总体原理非常简单(因为上面的那幅图画起来很费劲,这里就不想再画图了),只要想象上图之中的Passport服务器同时又是另外一个或多个其他Passport服务器的客户端,就会觉得一切都容易起来:这个Passport服务器先作为Google,Live,Yahoo等网站的帐户服务的客户端,将这个服务都整合在一起,然后作为一个统一的Passport提供给自身的一个或多个网站的栏目。

例如,参照如下流程:
1.用户打开http://www.dituren.cn/ ,并点击右上角的“登录”;
2.页面会转向到http://account.step1.cn/account/login.aspx ,开始进行登录,也就是文章开头看到的那张图片;
3.在页面上显示了多种帐户来源类型,用户选择自己有帐号的一种(以Google为例);
4.帐户服务器会将用户转向到Google的登录页面(此接口由Google Accounts Authentication 提供);
5.Google会首先让用户输入帐号密码,然后出现一个页面提示用户第三方网站正在请求帐户信息,请用户确认;
6.用户确认后,则Google会返回到一个页面,这个页面URL是在第4步的时候随参数发送给Google的;
7.在这个页面上接收Google传递的参数,写入到Cookie,然后将用户再次转向到最终栏目页面;
8.栏目写入Cookie,整个登录过程完成。

这个过程所起来比单点登录难不了多少,不过最大的问题在于这些网站提供的帐户服务互不相同,而且文档并不是很健全,目前研究这个服务的人又不多(国内更加少了),因此开发的时候不停的遇到很多很郁闷的问题(至今我都没有使用Google的OAuth成功登录,后来只好用AuthSub),因为不停的遇到一些小问题而且没有代码参考,心情十分郁闷,在最郁闷的时候,我决定将这所有的代码都整个公开提供下载,以减少还有别的同样对这个感兴趣的人研究的时候走的弯路。
我会首先顺序次在博客上贴出每一个网站的帐户接口的详细使用和代码,等到这个服务完善之后,就将整个源码提供下载。

预览地址是http://account.step1.cn/account/login.aspx ,目前就能登录,登录之后什么都不能做,呵呵!

1.程序结构

上次粗略的讲解了Step1帐户登录系统的思路之后,这一次我将介绍一下我的程序实现的结构,从这篇文章之后,就将细化到每一个接口,例如Google,Yahoo,Live等,对这些接口的使用进行详细的介绍。

先看看文件结构,说起来很简单因为一共只有以下三个文件:

1.Login.aspx,最重要的页面,在用户登录时让用户选择采用哪种帐号登录,用户选择对应的帐号之后,将用户转向到对应的网址;

2.Logout.aspx,将用户注销,并转向到原来的URL;

3.Handler.aspx,接收帐户服务器(例如Google的服务器)回发的登陆请求,根据回传的资料请求用户的信息,设置到Cookie并将用户转向到原先请求的网址;

再看看配置,为便于扩展和开发,我采用了在Web.config文件之中建立XML格式的配置的方法,我的网站目前的配置如下(其中的***是本站的私有Key,因此被我屏蔽了):

Web.Config配置

1 <accountserverconfiguration rooturl="http://account.step1.cn/account/" paramname="Step1AC" cookiedomain="step1.cn" cookiename="Step1ATS"><br>2 <accountservers><br>3 <accountserver name="google.com" urldata="http://www.google.com/m8/feeds/contacts/default/thin?max-results=0" urlscope="http://www.google.com/m8/feeds/" urlauthsubrequest="https://www.google.com/accounts/AuthSubRequest?" type="Step1.AccountServer.AccountServers.AuthSubServer,Step1.AccountServer"><br>4 <accountserver name="yahoo.com" type="Step1.AccountServer.AccountServers.BBAuthServer,Step1.AccountServer" pathpwtoken_login="/WSLogin/V1/wspwtoken_login?" pathlogin="/WSLogin/V1/wslogin?" server="https://api.login.yahoo.com" timeout="300" secret="a6e8**********52d1654f0" appid="CQLE4v*****************N.WXQbRg--"><br>5 <accountserver name="live.com" type="Step1.AccountServer.AccountServers.LiveServer,Step1.AccountServer" secret="account.step1.cn" appid="000000004000****" securityalgorithm="wsignin1.0"><br>6 <accountserver name="xiaonei.com" type="Step1.AccountServer.AccountServers.OpenSocialServer,Step1.AccountServer" loginurl="http://apps.xiaonei.com/passport/login.html"><br>7 </accountserver></accountserver></accountserver></accountserver></accountservers><br>8 </accountserverconfiguration>
9

简单的介绍一下配置项的内容,根节点AccountServerConfiguration表明这是一个帐户登录系统配置的内容,而它的几个属性是登录系统的全局配置,cookieName代表写入到Cookie的键名称,cookieDomain是写入Cookie的域,paramName是向客户Web栏目回传登录信息的参数名称,rootUrl是整个程序的部署URL。

AccountServers节点下的每一个AccountServer代表一个种登录类型服务器,属性name是该类型的唯一标示,属性type是该类型对应的实现类的名称,其他所有的属性都是对该类型的配置参数,例如对于Yahoo 的BBAuth,就必须配置在Yahoo登记的appid等。

了解文件结构和配置结构之后,再对照一下类的设计结构:

Step1帐户登录系统(1.程序结构)

上图之中的LoginPage.cs,LogoutPage.cs,HandlePage.cs分别代表上面提到的3个页面,Configuration.cs是用来读取上图提到的XML配置的类,AccountHelper.cs包含一些静态方法,例如读写用户Cookie等,其中最重要的是BaseServer,以及AccountServers目录下的那些类。

AccountServers都继承BaseServer,每一个类都代表一种登陆类型,例如OAuth,OpenID,Google的AuthSub等(其中的有些我还没有完全实现),因此添加新的类型支持,只需要在AccountServers文件夹之中添加一个对BaseServer的继承即可。

Tools文件夹包含一些附加的工具类,例如对WebService的访问等等。

因为我设计的时候,并没有从开始就考虑使用一个非常结构化的模式来设计,因此这个结构设计也显得比较简单,不过我个人比较喜欢这种简单明了的设计。本来计划这篇文章会包含代码的,可是发现现在已经很长了,只好在下一篇再贴上上面的一些重要代码

2.基础代码

在前面的文章之中,我介绍了一下Step1帐户登录系统的基本编程架构,而在这篇文章,将直接贴出相关的源码,由于这次的开发比较仓促,代码存在很多不完善的地方,因此,适合仅仅用来作为如何实现的代码,而不是适合直接使用,废话少说,直接看代码:

首先是登录页面的代码,代码分为代码文件和页面文件两个部分:

Login.aspx代码文件

1 public partial class LoginPage : System.Web.UI.Page
2 {
3 public string url,returnUrl=null;
4 public System.Collections.Specialized.NameValueCollection userInfo=null;
5 protected void Page_Load(object sender, EventArgs e)
6 {
7 url = Request.QueryString["url"];//获得登录完成后回转的URL
8 if (url == null || url.Length <= 0)
9 {
10 if (Request.UrlReferrer != null)
11 {
12 url = Request.UrlReferrer.ToString();
13 }
14 }
15 userInfo=AccountHelper.getUserInfo();//获得当前已经登录的用户信息
16 if (userInfo != null && url != null && url.Length > 1)
17 {
18 returnUrl = AccountHelper.getReturnUrl(url);//如果已经登陆,则直接将回转地址显示在页面的连接上
19 }
20 BaseServer server = AccountHelper.getServerByName(Request["ass"]);//如果通过ass参数指定了登录的类型(用户已经点击图标登录)
21 if (server != null)
22 {
23 if (url != null && url.Length > 0)
24 {
25 AccountHelper.saveUrl(url);//将登录回转的地址记录到Cookie
26 }
27 Response.Redirect(server.getLoginUrl(),true);//转向到相应的登录页面
28 }
29 }
30 }

下面是登录页面的页面文件,实际上就是显示登录界面的HTML内容:

Login.aspx页面文件

1
2
3
4
5
6


8 live.gif
9

10 google.gif
11

12 yahoo.gif
13

14 xiaonei.gif
15

17 Live,MSN,Hotmail用户
18

19 Google,Gmail用户
20

21 Yahoo,Flickr用户
22

23 校内网用户(即将推出)
24

26
27

28 ,您好!您已经使用 账号登录
29 点击返回
30

31
32
说明:

33
34

上可以看出,服务端支持哪几种登录方式和登录界面完全没有关系,前台登录界面并不是自动生成的。

然后是注销的Logout.aspx代码,注销的代码因为没有界面,因此没有页面文件(空文件),仅仅有一个代码文件:

Logout.aspx代码

1 public partial class LogoutPage : System.Web.UI.Page
2 {
3 protected void Page_Load(object sender, EventArgs e)
4 {
5 //退出系统,如果URL不存在,则返回登录页
6 string url = Request.QueryString["url"];
7 if (url == null || url.Length <= 0)
8 {
9 url = Request.UrlReferrer.ToString();
10 }
11 AccountHelper.clearCookie();//清除用户的Cookie
12 AccountHelper.redirect(url);
13 }
14 }

注销的代码要简单得多,不过不是最简单的,最简单的是Handler.aspx,这个文件之所以简单是因为同样没有页面文件,再加上其中的代码文件所做的事情都已经调用其它的类来完成,因此只有一个非常简单的代码文件:

Handler.aspx代码文件

1public partial class HandlePage : System.Web.UI.Page
2{
3 protected void Page_Load(object sender, EventArgs e)
4 {
5 BaseServer server = AccountHelper.getServerByName(Request["ass"]);//根据ASS参数找到对应的服务器类型对象
6 server.parseHandle(this.Context);//由该对象来处理返回请求
7 }
8});

在上面的三个aspx文件的代码之中,无一例外的调用了AccountHelper类,这个类包含一些重要的静态方法,内容如下:

AccountHelper.cs

1 public class AccountHelper
2 {
3 //清除Cookie
4 public static void clearCookie()
5 {
6 HttpCookie loginCookie = new HttpCookie(Configuration.Instance().cookieName);
7 loginCookie.Expires = DateTime.Now.AddYears(-10);
8 loginCookie.Domain = Configuration.Instance().cookieDomain;
9 loginCookie.HttpOnly = false;
10 HttpContext.Current.Response.Cookies.Add(loginCookie);
11 }
12 //将用户信息加入到Cookie
13 public static void setUserInfo(string account, string name, string type)
14 {
15 HttpCookie loginCookie = new HttpCookie(Configuration.Instance().cookieName);
16 loginCookie.Domain = Configuration.Instance().cookieDomain;
17 loginCookie.Values.Add("account", Convert.ToBase64String(Encoding.UTF8.GetBytes(account)));
18 loginCookie.Values.Add("name", Convert.ToBase64String(Encoding.UTF8.GetBytes(name)));
19 loginCookie.Values.Add("type", Convert.ToBase64String(Encoding.UTF8.GetBytes(type)));
20 loginCookie.Expires = DateTime.Now.AddYears(1);
21 HttpContext.Current.Response.Cookies.Add(loginCookie);
22 }
23 //从Cookie之中获取用户信息
24 public static NameValueCollection getUserInfo()
25 {
26 HttpCookie cookie = HttpContext.Current.Request.Cookies[Configuration.Instance().cookieName];
27 if (cookie != null)
28 {
29 NameValueCollection userInfo = new NameValueCollection();
30 foreach (string key in cookie.Values)
31 {
32 userInfo.Add(key,Encoding.UTF8.GetString(Convert.FromBase64String(cookie.Values[key])));
33 }
34 return userInfo.HasKeys()?userInfo:null;
35 }
36 return null;
37 }
38 //保存URL以便在完成后转向
39 public static void saveUrl(string url)
40 {
41 HttpCookie loginCookie = new HttpCookie(Configuration.Instance().cookieName + "_url");
42 loginCookie.Value = url;
43 loginCookie.Domain=Configuration.Instance().cookieDomain;
44 loginCookie.Expires = DateTime.Now.AddDays(1);
45 HttpContext.Current.Response.Cookies.Add(loginCookie);
46 }
47 //将用户的登录参数加入到URL并回转给Web应用
48 public static void returnOpener()
49 {
50 HttpCookie urlCookie = HttpContext.Current.Request.Cookies[Configuration.Instance().cookieName + "_url"];
51 string url = getReturnUrl(urlCookie == null ? "./" : urlCookie.Value);
52 urlCookie.Expires = DateTime.Now.AddYears(-10);
53 HttpContext.Current.Response.Cookies.Add(urlCookie);
54 redirect(url);
55 }
56 //根据已登陆用户信息和回转的基础URL地址得到回转的URL
57 public static string getReturnUrl(string url)
58 {
59 url += (url.IndexOf("?") > 0) ? "&" : "?";
60 System.Collections.Specialized.NameValueCollection userInfo = HttpContext.Current.Request.Cookies[Configuration.Instance().cookieName].Values;
61 string[] infoArr = new string[userInfo.AllKeys.Length];
62 userInfo.CopyTo(infoArr, 0);
63 url += Configuration.Instance().paramName + "=" + HttpContext.Current.Server.UrlEncode(string.Join(",", userInfo.AllKeys) + ";" + string.Join(",", infoArr));
64 return url;
65 }
66 //转向到指定URL,否则转向到登录页
67 public static void redirect(string url)
68 {
69 if (url == null || url.Length <= 0)
70 {
71 HttpContext.Current.Response.Redirect("Login.aspx");
72 }
73 else
74 {
75 HttpContext.Current.Response.Redirect(url);
76 }
77 }
78 //根据帐户类型的名称返回对应的帐户服务对象
79 public static BaseServer getServerByName(string name)
80 {
81 if (name == null || name.Length < 1) { return null; }
82 BaseServer[] servers=Configuration.Instance().AccountServers;
83 for (int i = 0; i < servers.Length; i++)
84 {
85 if (servers[i].name == name)
86 {
87 return servers[i];
88 }
89 }
90 return null;
91 }
92 public static string getHandleUrl()
93 {
94 return Configuration.Instance().rootUrl + "Handler.aspx";
95 }
96 public static string getLoginUrl()
97 {
98 return Configuration.Instance().rootUrl + "Login.aspx";
99 }
100 }

上面的代码就是比较长的一段了,不过确实是比较重要的一些方法,而下面的BaseServer.cs是一个抽象类,所有的登录类型(例如Google AuthSub类型,Yahoo BBAuth类型)都继承自此类,此类本身不能实例化:

BaseServer.cs

1 public abstract class BaseServer
2 {
3 public string name;
4 public BaseServer(System.Xml.XmlNode node)
5 {
6 for (int i = 0; i < node.Attributes.Count; i++)
7 {
8 switch (node.Attributes[i].LocalName)
9 {
10 case "name":
11 name = node.Attributes[i].Value;
12 break;
13 }
14 }
15 }
16 public virtual string getLoginUrl()
17 {
18 return "";
19 }
20 public virtual void parseHandle(HttpContext page)
21 {
22 }
23 public virtual string getHandleUrl()
24 {
25 return AccountHelper.getHandleUrl()+"?ass=" + name;
26 }
27 }

到这里,这些基础的类就都介绍完毕,我之所以不厌其烦的将这些代码都贴上来,主要是为后面介绍每一种登录类型的时候,能够比较清晰的看出是登录过程如何实现的,上面的代码都比较简单,因为都是本站自己的逻辑,但是当涉及到和Google、Yahoo等的帐户服务器交互的时候,很多时候必须完全按照对应的接口来做,因此会比较难懂

3.使用Google的Auth Sub登录网站

前面的文章之中,我介绍了Step1帐户登录系统的基本实现架构和代码,从这一篇开始,我开始逐次讲解各种帐户的登录过程,在文章的最后,我都会贴出相应的代码,在代码之中有很多调用了前一篇文章之中讲到的基础代码,因此,可能需要对照才能明白。

先从Google的AuthSub开始讲起,为什么呢,因为AuthSub最简单,实现起来很容易,记得我开始焦头烂额的研究了一个多星期Google的OAuth,始终没有成功,可是当我改成调用AuthSub之后,几个小时就实现了整个登录过程。

有关Google AuthSub的文档和相关的内容,请参考:AuthSub Authentication for Web Applications

废话少说,现直接看一个Google的AuthSub登陆过程介绍图片:

2008126103941491.png

1.生成用来转向给Google认证系统的网址并转向(图中1);

2.接受从Google认证系统回转的页面参数(图中4)

3.使用Google的返回的参数向Google请求用户信息(图中5和6)

现在,让我们来按照顺序完成以上流程:

首先,你的程序必须在Google注册才能调用此接口,因此先到Google的Manage your domains 页面注册你的程序,注册界面如下:

2008126103941414.jpg

1."Target URL path prefix"只是个前缀而已,并不是完整的回转的路径,在生成登录的时候,还是要将回转的路径发送给Google,而且必须和这里注册的前缀相符,这应该算是用来保障安全的一个设置。

前面的文章之中,我介绍了Step1帐户登录系统的基本实现架构和代码,从这一篇开始,我开始逐次讲解各种帐户的登录过程,在文章的最后,我都会贴出相应的代码,在代码之中有很多调用了前一篇文章之中讲到的基础代码,因此,可能需要对照才能明白。

先从Google的AuthSub开始讲起,为什么呢,因为AuthSub最简单,实现起来很容易,记得我开始焦头烂额的研究了一个多星期Google的OAuth,始终没有成功,可是当我改成调用AuthSub之后,几个小时就实现了整个登录过程。

有关Google AuthSub的文档和相关的内容,请参考:AuthSub Authentication for Web Applications

废话少说,现直接看一个Google的AuthSub登陆过程介绍图片:

2008126103941491.png

1.生成用来转向给Google认证系统的网址并转向(图中1);

2.接受从Google认证系统回转的页面参数(图中4)

3.使用Google的返回的参数向Google请求用户信息(图中5和6)

现在,让我们来按照顺序完成以上流程:

首先,你的程序必须在Google注册才能调用此接口,因此先到Google的Manage your domains 页面注册你的程序,注册界面如下:

2008126103941414.jpg

1."Target URL path prefix"只是个前缀而已,并不是完整的回转的路径,在生成登录的时候,还是要将回转的路径发送给Google,而且必须和这里注册的前缀相符,这应该算是用来保障安全的一个设置。

前面的文章之中,我介绍了Step1帐户登录系统的基本实现架构和代码,从这一篇开始,我开始逐次讲解各种帐户的登录过程,在文章的最后,我都会贴出相应的代码,在代码之中有很多调用了前一篇文章之中讲到的基础代码,因此,可能需要对照才能明白。

先从Google的AuthSub开始讲起,为什么呢,因为AuthSub最简单,实现起来很容易,记得我开始焦头烂额的研究了一个多星期Google的OAuth,始终没有成功,可是当我改成调用AuthSub之后,几个小时就实现了整个登录过程。

有关Google AuthSub的文档和相关的内容,请参考:AuthSub Authentication for Web Applications

废话少说,现直接看一个Google的AuthSub登陆过程介绍图片:

2008126103941491.png

1.生成用来转向给Google认证系统的网址并转向(图中1);

2.接受从Google认证系统回转的页面参数(图中4)

3.使用Google的返回的参数向Google请求用户信息(图中5和6)

现在,让我们来按照顺序完成以上流程:

首先,你的程序必须在Google注册才能调用此接口,因此先到Google的Manage your domains 页面注册你的程序,注册界面如下:

2008126103941414.jpg

1."Target URL path prefix"只是个前缀而已,并不是完整的回转的路径,在生成登录的时候,还是要将回转的路径发送给Google,而且必须和这里注册的前缀相符,这应该算是用来保障安全的一个设置。

2.OAuth Consumer Key和OAuth Consumer Secret在OAuth认证的时候相当的重要,不过在AuthSub之中是没有用到的。

注册完成之后,就要开始生成登录的URL,按照Google的文档,AuthSub的登陆URL应该是https://www.google.com/accounts/AuthSubRequest 加上一些参数组成,我用到了两个参数:

1.next参数,指定用户登录完成之后回转的URL地址,注意必须以上面注册的"Target URL path prefix"开头;

2.scope参数,指定需要访问哪些资源,这里的资源是以URL来描述的,例如我们在系统之中需要得到用户的登录用户名,所以要访问用户的通讯录(Google登录回转参数并不包含用户名),因此可以从Google Contacts Data API 上查到需要使用的scope为http://www.google.com/m8/feeds/,注意,如果需要访问多个资源,则以空格隔开每个URL

结合以上两个参数(注意,每一个参数都必须经过URL编码),就可以的到一个URL地址,例如https://www.google.com/accounts/AuthSubRequest?next=http%3a%2f%2faccount.step1.cn%2faccount%2fHandler.aspx%3fass%3dgoogle.com&scope=http%3a%2f%2fwww.google.com%2fm8%2ffeeds%2f,我们将用户转向过去,用户就会到达Google的登陆界面(假如用户已经登录到Google,则会跳过此界面):

2008126103942403.jpg

Step1帐户登录系统(3.使用Google的Auth Sub登录网站)

用户只有点击“授予访问权”才会正常登录,在登录完成之后,Google会将用户转向到访问你在上面的next参数之中指定的URL地址,并在地址之中加入一个token参数包含一个访问令牌,例如这个网址:http://account.step1.cn/account/Handler.aspx?ass=google.com&token=CJaHiYeKEBCE7qWi-v____8B

我们只需要在回转页面(我系统之中的Hander.aspx)之中对token参数进行处理即可,需要说明的是,我不愿意为每一种登录类型都建立一个回转页面,这样会带来维护上的麻烦,因此我的所有回转页面都是Handler.aspx然后通过ass参数进行区分。

下一步就是根据我们已经得到的token参数获取用户的用户名(E-mail地址),我研究了好久,最后发现发现只需要访问用户的地址本即可,因为用户即使地址本之中没有任何内容,返回的内容也包含他本人的E-mail地址,因此,我通过HttpWebRequest访问如下网址:http://www.google.com/m8/feeds/contacts/default/thin?max-results=0

,这个大体含义是以最简单的结果(thin)返回地址本之中的0条记录,当然,访问的时候要使用刚才获取到的token令牌,否则会得到需要认证的提示,具体的方法请参考下面的代码。

最后,将得到的用户名和用户昵称保存到Cookie,大功告成!

以下是我实现的以上过程的类AuthSubServer.cs的源码:

AuthSubServer.cs的源码

1 public class AuthSubServer:BaseServer
2 {
3 private string urlAuthSubRequest, urlScope, urlData;
4 //采用Web.Config之中的XML节点作为构造函数参数
5 public AuthSubServer(System.Xml.XmlNode node)
6 : base(node)
7 {
8 for (int i = 0; i < node.Attributes.Count; i++)
9 {
10 switch (node.Attributes[i].LocalName)
11 {
12 case "urlAuthSubRequest":
13 urlAuthSubRequest = node.Attributes[i].Value;
14 break;
15 case "urlScope":
16 urlScope = node.Attributes[i].Value;
17 break;
18 case "urlData":
19 urlData = node.Attributes[i].Value;
20 break;
21 }
22 }
23 }
24 public override string getLoginUrl()//生成登录的URL
25 {
26 return urlAuthSubRequest + "next=" + HttpUtility.UrlEncode(getHandleUrl()) + "&scope=" + HttpUtility.UrlEncode(urlScope);
27 }
28 public override void parseHandle(HttpContext page)//处理回转请求
29 {
30 string token = page.Request["token"];
31 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(urlData));//访问通讯录数据
32 request.Headers.Add("Authorization", "AuthSub token="" + token + """);//这一句将token令牌加入到数据访问请求之中
33 request.Method = "GET";
34 HttpWebResponse response = (HttpWebResponse)getResponse(request);
35 XmlDocument doc = new XmlDocument();
36 if (response != null)
37 {
38 doc.Load(response.GetResponseStream());
39 XmlNode node = doc.SelectSingleNode("*/*[local-name()='id']");//读取用户的ID(E-mail地址)
40 string account = node != null ? node.InnerText : "";
41 node = doc.SelectSingleNode("*/*[local-name()='author']/*[local-name()='name']");//读取用户昵称
42 string name = node != null ? node.InnerText : "";
43 AccountHelper.setUserInfo(account, name, this.name);
44 AccountHelper.returnOpener();
45 page.Response.End();
46 }
47 }
48 public static HttpWebResponse getResponse(HttpWebRequest request)
49 {
50 try
51 {
52 return (HttpWebResponse)request.GetResponse();
53 }
54 catch (WebException e)
55 {
56 HttpContext.Current.Response.Write(e.Message);
57 string result = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
58 HttpContext.Current.Response.Write(result);
59 HttpContext.Current.Response.End();
60 }
61 return null;
62 }
63 }

4.使用Windows Live ID登录网站

在上一篇文章之中,我具体的讲解了使用Google的Authsub接口来让自己的网站支持Google帐号登陆,今天要讲到的是Windows Live ID,即使用MSN或Hotmail的帐号来登录网站,因为其中的基础原理类似,因此,假如对登录的流程和原理不清楚的话,建议去看看我前面的几篇文章,在这里,我主要讲解具体的实现和源码。

关于Windows Live ID接口的更多信息,建议去参考查看Windows Live ID接口介绍网站的更多内容,我在这篇文章之中,仅仅会讲到Windows Live ID在Web Authentication上的一个应用。

首先,和Google AuthSub一样,你必须先在Live的云计算开发中心Azure Services Developer Portal去注册你的程序,其实几个星期前我注册程序的时候应该还不叫这个比较时髦的名字的,今天我因为写这个文章再上去一看,居然变成“云”了,这么说来,我已经“云计算”了一把了,呵呵!

现在的注册界面如图(我这里截图是修改界面,添加界面和这个类似):

2008126103938516.gif

注册完成之后,就可以开始进行开发,需要说明的是,在Live ID站点上提供了一个非常简单的类WindowsLiveLogin.cs,应该可以在这个例子之中得到这个类:Running the C# QuickStart Sample,而且可能是因为有了这个类,Microsoft觉得不需要再去提供什么和服务端交互的文档了(反正我没有找到),因此,我就直接不改动任何程序的情况下使用了这个类,这样,一切就方便了很多了。

先看如何得到用来让用户登录的转向地址,这个太容易了,因为这正是WindowsLiveLogin类的方法,需要说明的是,WindowsLiveLogin类有几个方法来获得转向地址的,其中GetLoginUrl方法得到的地址仅仅进行用户登录,而不向用户申请任何权限,而GetConsentUrl(string scope)方法可以指定向用户申请读取相应数据的权限,和Google AuthSub一样,我们在使用Live的登录的时候也需要读取用户的地址本信息来获得用户的登录帐号地址,因此,我们使用wll.GetConsentUrl("Contacts.View");方法来获得转向URL,("Contacts.View"代表对地址本的只读访问),当然在使用这个方法之前,要先设置好PolicyUrl属性(隐私申明地址)和ReturnUrl属性(登录完成之后的回转地址);

有一点需要特别说明的是:在使用GetLoginUrl或者GetConsentUrl方法之前,Live要求用户必须必须已经指定WindowsLiveLogin的PolicyUrl属性,也就是隐私申明的地址,而且,要求指定的网址必须是能够访问的,似乎Live还会去检查这个网址是否能够访问的,我在系统之中将隐私申明的网址指定为我的登录页面,因为在那个页面有我关于保护用户隐私的申明。

将用户转向到刚才获取的URL之后,用户就会被转向到Live ID的登录页:

2008126103939908.gif

Live转回到登录回转地址的时候,会有多种参数(delauth,login,logout,clearcookie),根据Live接口的要求,这些类型都应该实现,具体每个类型代表如下含义:

1.delauth,这个是我主要用到的类型,就是在用户登录并授与访问权限之后,回转的类型,注意,必须调用GetConsentUrl方法才会得到这种回转,从参数之中可以获取到ConsentToken参数,就是后面要用到的数据访问令牌。

2.login,这是调用GetLoginUrl方法之后回转的类型,也就是说,是不需要访问用户的任何数据的情况下回转访问的参数类型;

3.logout,注销

4.clearcookie,清除Cookie

在本系统之中,仅仅使用了delauth,从参数之中获得ConsentToken参数,然后,我们可以使用这个数据令牌去获取用户的帐户名称和昵称,同样,也是访问Windows Live Contacts接口,因为Windows Live Contacts接口不是我要讲的主题,因此不做详细的介绍,需要了解我如何使用,参考我的代码即可。

取得用户的登录ID和昵称之后,写入到用户的Cookie之中,然后登录回转,采用Live登录的过程就算完成了,还有什么不清楚的地方的话,请参考我下面贴出的整体代码:

LiveServer.cs代码

1 public class LiveServer : BaseServer
2 {
3 public WindowsLiveLogin wll;
4 //采用Web.Config之中的XML节点作为构造函数参数
5 public LiveServer(System.Xml.XmlNode node):base(node)
6 {
7 wll = new WindowsLiveLogin(false);
8 for (int i = 0; i < node.Attributes.Count; i++)
9 {
10 switch (node.Attributes[i].LocalName)
11 {
12 case "appid":
13 wll.AppId = node.Attributes[i].Value;
14 break;
15 case "secret":
16 wll.Secret = node.Attributes[i].Value;
17 break;
18 case "securityalgorithm":
19 wll.SecurityAlgorithm = node.Attributes[i].Value;
20 break;
21 }
22 }
23 }
24 //采用回传得到的令牌获取用户的信息
25 public XmlDocument getData(string token)
26 {
27 string lid = wll.ProcessConsentToken(token).LocationID;
28 //访问用户的地址本
29 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("https://livecontacts.services.live.com/users/@L@" + lid + "/rest/LiveContacts/owner"));
30 request.Method = "GET";
31 request.Headers.Add("Authorization", "DelegatedToken dt="" + wll.ProcessConsentToken(token).DelegationToken + """);
32 HttpWebResponse response = getResponse(request);
33 XmlDocument doc = new XmlDocument();
34 if (response != null)
35 {
36 doc.Load(response.GetResponseStream());
37 }
38 return doc;
39 }
40 public static HttpWebResponse getResponse(HttpWebRequest request)
41 {
42 try
43 {
44 return (HttpWebResponse)request.GetResponse();
45 }
46 catch (WebException e)
47 {
48 HttpContext.Current.Response.Write(e.Message);
49 string result = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
50 HttpContext.Current.Response.Write(result);
51 HttpContext.Current.Response.End();
52 }
53 return null;
54 }
55 //得到用户的登录URL地址
56 public override string getLoginUrl()
57 {
58 wll.PolicyUrl = AccountHelper.getLoginUrl();
59 wll.ReturnUrl = getHandleUrl();
60 return wll.GetConsentUrl("Contacts.View");
61 //return wll.GetLoginUrl();
62 }
63 //处理登录回转信息
64 public override void parseHandle(HttpContext page)
65 {
66 string action = page.Request["action"];
67 switch (action)
68 {
69 case "delauth":
70 //获得用户地址本XML数据
71 XmlDocument doc = getData(page.Request["ConsentToken"]);
72 //获取用户帐号
73 XmlNode node = doc.SelectSingleNode("Owner/WindowsLiveID");
74 string account = node != null ? node.InnerText : "";
75 //获取用户的昵称
76 node = doc.SelectSingleNode("Owner/Profiles/Personal/DisplayName");
77 string name = node != null ? node.InnerText : "";
78 //设置用户信息到Cookie
79 AccountHelper.setUserInfo(account, name, this.name);
80 //回转
81 AccountHelper.returnOpener();
82 page.Response.End();
83 break;
84 case "login"://这是Live接口要求定义支持的类型,系统之中没有主动使用这种请求
85 WindowsLiveLogin.User user = wll.ProcessLogin(page.Request.Form);//从URL参数之中解析出用户的登录信息
86 AccountHelper.setUserInfo(user.Id, user.Id, this.name);//这里的user.ID实际上已经是用户的E-mail
87 AccountHelper.redirect(wll.GetConsentUrl("Contacts.View", user.Token));
88 page.Response.End();
89 break;
90 case "logout"://这是Live接口要求定义支持的类型,系统之中没有主动使用这种请求
91 AccountHelper.clearCookie();
92 AccountHelper.returnOpener();
93 page.Response.End();
94 break;
95 case "clearcookie"://这是Live接口要求定义支持的类型,系统之中没有主动使用这种请求
96 default:
97 AccountHelper.clearCookie();
98 string type;
99 byte[] content;
100 wll.GetClearCookieResponse(out type, out content);
101 page.Response.ContentType = type;
102 page.Response.OutputStream.Write(content, 0, content.Length);
103 page.Response.End();
104 break;
105
106 }
107 }
108 }

5.使用Yahoo BBAuth登录网站

从系列之中的上一篇文章,我介绍了如何支持Windows Live ID来登录自己的网站,这一篇按照顺序,我介绍如何支持使用Yahoo BBAuth来登录网站,和上面的一篇文章一样,我将会仅仅对具体的逻辑进行实现,不再重复基础的原理。

关于Yahoo BBAuth的更多信息,请参考:Browser-Based Authentication。

可以先看Yahoo提供的BBAuth的原理示意图:

2008126103935936.png

2008126103935915.gif

2008126103936286.gif

好,闲话少说,在经过注册之后,得到了这样几个参数:appid,secret,这几个参数在程序之中需要使用的。

先看如何得到用来让用户登录的转向地址,这个非常容易,那几个参数也是现成的,只是格式是严格要求的,一定不能出错,否则反正Yahoo的BBAuth服务器反正就是给你出个错,让你摸不着头脑了,详细的代码还是在下面看看代码吧。

得到转向地址并转向给Yahoo之后,就可以将用户转向到Yahoo的公共登录界面,用户登录之后,就会出现关于登录许可的提示,用户只有点击同意才能继续登录的过程。

2008126103936131.gif

1.检查回传得Token是否正常;

2.根据回传的Token向Yahoo的服务器请求WSSID;

3.根据WSSID向Ymail接口请求用户的E-mail地址;

得到E-mail地址之后,就完成了整个登录过程。

下面是实现过程的代码:

BBAuthServer.cs代码

1 public class BBAuthServer:BaseServer
2 {
3 private string appid, secret, server, pathLogin, pathPwtoken_login;
4 private int timeout=300;
5 //采用Web.Config之中的XML节点作为构造函数参数
6 public BBAuthServer(System.Xml.XmlNode node)
7 : base(node)
8 {
9 for (int i = 0; i < node.Attributes.Count; i++)
10 {
11 switch (node.Attributes[i].LocalName)
12 {
13 case "appid":
14 appid = node.Attributes[i].Value;
15 break;
16 case "secret":
17 secret = node.Attributes[i].Value;
18 break;
19 case "server":
20 server = node.Attributes[i].Value;
21 break;
22 case "pathLogin":
23 pathLogin = node.Attributes[i].Value;
24 break;
25 case "pathPwtoken_login":
26 pathPwtoken_login = node.Attributes[i].Value;
27 break;
28 case "timeout":
29 timeout = int.Parse(node.Attributes[i].Value);
30 break;
31 }
32 }
33 }
34 public bool checkRequest(HttpRequest Request)
35 {
36 string ts = Request["ts"];
37 string sig = Request["sig"];
38 //先检查时间
39 if (Math.Abs(long.Parse(ts) - ((long)((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds))) > timeout)
40 {
41 // throw new Exception("Error parsing timeout.");
42 }
43 //再检查签名
44 string baseString = System.Text.RegularExpressions.Regex.Replace(Request.Url.PathAndQuery, "&sig=[^&]+", "");
45 if (System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(baseString + secret, "MD5").ToLower() != sig)
46 {
47 throw new Exception("Signature mismatch:" + baseString);
48 }
49 return true;
50 }
51 public string getWSSID(string token,out string cookie)
52 {
53 string ts = ((long)((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)).ToString();
54 string baseString = pathPwtoken_login+"appid=" + HttpUtility.UrlEncode(appid) + "&token=" + HttpUtility.UrlEncode(token) + "&ts=" + ts + "";
55 string sig = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(baseString + secret, "MD5").ToLower();
56 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(server + baseString + "&sig=" + sig));
57 request.Method = "GET";
58 HttpWebResponse response = (HttpWebResponse)request.GetResponse();
59 XmlDocument doc = new XmlDocument();
60 doc.Load(response.GetResponseStream());
61 cookie = doc.SelectSingleNode("//*[local-name()='Cookie']").InnerText.Trim().Substring(2);
62 return doc.SelectSingleNode("//*[local-name()='WSSID']").InnerText.Trim();
63 }
64 public string getUserName(string token,string wssid,string cookie)
65 {
66 try
67 {
68 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://mail.yahooapis.com/ws/mail/v1.1/soap?appid=" + HttpUtility.UrlEncode(appid) + "&WSSID=" + HttpUtility.UrlEncode(wssid)));
69 request.Method = "POST";
70 request.CookieContainer = new System.Net.CookieContainer();
71 CookieCollection collection = new CookieCollection();
72 request.CookieContainer.Add(new Uri("http://mail.yahooapis.com/"), new Cookie("Y", cookie));
73 byte[] bytes = Encoding.UTF8.GetBytes("<?xml version="1.0" encoding="utf-8"?><envelope mce_href="http://www.3648.com/" http: envelope soap schemas.xmlsoap.org www.3648.com xmlns:soap-env="&lt;a href=">http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="<a href="http://www.3648.com/" mce_href="http://www.3648.com/" http: xmlschema-instance www.w3.org>http://www.w3.org/1999/XMLSchema-instance</a>" xmlns:xsd="<a href="http://www.3648.com/" mce_href="http://www.3648.com/" http: www.w3.org xmlschema>http://www.w3.org/1999/XMLSchema</a>" SOAP-ENV:encodingStyle="<a href="http://www.3648.com/" mce_href="http://www.3648.com/" http: soap schemas.xmlsoap.org encoding><getuserdata>http://schemas.xmlsoap.org/soap/encoding/"&gt;<getuserdata a>xmlns="urn:yahoo:ymws"&gt;</getuserdata></getuserdata></a></envelope>");
74 request.Headers.Add("SOAPAction", """");
75 request.ContentType = "application/soap+xml; charset=utf-8";
76 request.ContentLength = bytes.Length;
77 Stream rs = request.GetRequestStream();
78 rs.Write(bytes, 0, bytes.Length);
79 rs.Close();
80 HttpWebResponse response = getResponse(request);
81 XmlDocument doc = new XmlDocument();
82 doc.Load(response.GetResponseStream());
83 return doc.SelectSingleNode("//*[local-name()='defaultID']").InnerText;
84 /**//*
85 ymws ymwsInstance = new ymws();
86 ymwsInstance.Url = "http://mail.yahooapis.com/ws/mail/v1.1/soap?appid=" + appid + "&wssid=" + wssid;
87 ymwsInstance.CookieContainer = new System.Net.CookieContainer();
88 CookieCollection collection = new CookieCollection();
89 ymwsInstance.CookieContainer.Add(new Uri("http://mail.yahooapis.com/"), new Cookie("Y", cookie));
90 GetUserDataResponse userData = ymwsInstance.GetUserData(new GetUserData());
91 return userData.data.userSendPref.defaultID;*/
92 }
93 catch (Exception)
94 {
95 return wssid;
96 }
97 }
98 public override string getLoginUrl()//生成登录的URL
99 {
100 string ts = ((long)((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)).ToString();
101 string baseString = pathLogin+"appid=" + HttpUtility.UrlEncode(appid) + "&appdata=" + HttpUtility.UrlEncode(name) + "&send_userhash=1&ts=" + ts + "";
102 string sig = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(baseString + secret, "MD5").ToLower();
103 return server + baseString + "&sig=" + sig;
104 }
105 public override void parseHandle(HttpContext page)//处理回转请求
106 {
107 checkRequest(page.Request);//检查回传请求是不是合法
108 string cookie;
109 string wssid = getWSSID(page.Request["token"],out cookie);//先获取wssid
110 string name = getUserName(page.Request["token"], wssid, cookie);//通过wssid获取用户名
111 //检查完毕,开始获得用户的WSSID
112 AccountHelper.setUserInfo(page.Request["userhash"], name, this.name);
113 AccountHelper.returnOpener();
114 page.Response.End();
115 }
116 public static HttpWebResponse getResponse(HttpWebRequest request)
117 {
118 try
119 {
120 return (HttpWebResponse)request.GetResponse();
121 }
122 catch (WebException e)
123 {
124 HttpContext.Current.Response.Write(e.Message);
125 string result = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
126 HttpContext.Current.Response.Write(result);
127 HttpContext.Current.Response.End();
128 }
129 return null;
130 }
131 }

6.使用OpenID登录网站

在我昨天的博客之中,我我介绍了我在帐户登录系统中对Yahoo的BBAuth的登录的支持过程,上面那篇文章的代码虽然很简单,文章也不长,实际上却是我研究的最深刻的一种类型,也是我对BBAuth的研究最终让我决定将这个系统全部开源。,今天,我要讲一下,OpenID的实现,下一篇应该是讲如何支持校内网的登录,再后面我可能简单的讲讲Oauth登录(如果我确实能够研究清楚地话),有网友曾经问到什么时候会提供整个代码的下载,我并不是不提供,而是我觉得代码还非常不完善,我在网站上提供代码的下载肯定要在我每一个支持的登录类型都介绍完毕之后,当时一定要在元旦之前,如果确实在此之前需要参考一下我的代码,可以直接与我联系,我随时可以提供所有的源码。

讲到OpenID,想来很多读者都有一定的了解,没错,这是一个开放的协议,目的就是让用户能够在所有的网站使用一个账号和密码登录,这一点,和本系统的目的是不同的,因为本系统是比较自私的,希望是让所有的用户都可以使用自己已经有的帐户和密码登录,而OpenID则是希望所有的用户在所有的网站都可以使用一个账号和密码登录,当然要实现这个是比较难的,目前没有实现,但是不能不承认这是一个很有意义的事情,而本系统也不能不支持这种帐户的登录。

关于OpenID的更多信息建议到OpenID的相关网站去看看,说老实话,这是我很看好的一个项目,如果感兴趣可以看看OpenID的相关网站。

OpenID的实现原理和本系统,和Google的AuthSub,Yahoo的BBAuth,Live的Account Server,包括我还没有介绍到的Oauth,实现的原理都是一样的,都是采用一种可信的方式在用户和Web应用程序之间建立交互,所不同的是,OpenID因为是要做到开放和能够到处兼容,所以有一些其它的考虑,至于具体有一些什么考虑,我能了解的是这样几点:

1.采用URL作为账号,这样的话,就可以支持任意的网站,任何一个网站都可以提供OpenID了

2.用户提供的URL之中,通过http或者html的Header来指定OpenID服务器的地址,甚至可以指定牛转向到另一个OpenID地址,这样可以确保用户可以使用自己喜欢的网址来作为登录帐号

我知道的不比你多多少,我知道的也就是这些了,对于怎么才能支持OpenID登录,虽然我研究了很久,不过最后得出一个结论,还是使用公用的库吧,因为OpenID相对还是比较复杂的,要完整地支持还是挺难的;

我采用的是ExtremeSwank的Dotnet OpenID2.0的库,这个库确实很好用,很快我就完成了对OpenID的支持,因为我对OpenID的了解实际上并不够,因此也不太合适对OpenID进行什么长篇大论,因此,我也就简单的提供OpenID的支持相关代码了:

需要注意的是,以前我支持的多个类型都是只需要选择登录类型即可,而对于OpenID,因为必须先让用户输入登录帐号(URL),根据url地址才能够得到登录的转向地址,所以,必须先让用户输入帐号才能进行登录的转向。

因为在登录界面根据URL的不同而不同,所以这次在本站也不再列出登录相关的截图,仅仅提供实现的代码:

OpenidServer.js代码

1 public class OpenidServer : BaseServer
2 {
3 private string urlIdentity=null,defaultNickName=null;
4 //采用Web.Config之中的XML节点作为构造函数参数
5 public OpenidServer(System.Xml.XmlNode node)
6 : base(node)
7 {
8 for (int i = 0; i < node.Attributes.Count; i++)
9 {
10 switch (node.Attributes[i].LocalName)
11 {
12 case "urlIdentity"://如果指定了urlIdentity,就可以不让用户输入帐好了,就可以支持OpenID类型的网站类型登录了,例如指定为yahoo.com,则用户就可以直接支持Yahoo的登录了
13 urlIdentity = node.Attributes[i].Value;
14 break;
15 case "defaultNickName"://指定默认的昵称
16 defaultNickName = node.Attributes[i].Value;
17 break;
18 }
19 }
20 }
21 public override string getLoginUrl()//返回登录地址
22 {
23 OpenIDConsumer openid = new OpenIDConsumer(new NameValueCollection(), null, null);
24
25 SimpleRegistration sr = new SimpleRegistration(openid);
26 sr.AddRequiredFields(SimpleRegistrationFields.Nickname);//设置附加的字段列表
27
28 openid.ReturnURL = this.getHandleUrl();
29 openid.Identity = urlIdentity!=null?urlIdentity:HttpContext.Current.Request["openid_url"];
30 return openid.BeginAuth(false, false);//获得并返回登录地址
31 }
32 public override void parseHandle(HttpContext page)//回转内容处理函数
33 {
34 OpenIDConsumer openid = new OpenIDConsumer(page.Request.QueryString, null, null);
35 switch (openid.RequestedMode)
36 {
37 case RequestedMode.IdResolution:
38 if (openid.Validate())
39 {
40 OpenIDUser user = openid.RetrieveUser();
41 string account=user.Identity;
42 if (user.ExtensionData.ContainsKey(SimpleRegistrationFields.Email))
43 {
44 account = user.ExtensionData[SimpleRegistrationFields.Email];
45 }
46 string nickName;//虽然设置了附加的字段类型,可是服务器未必支持,因此还是要判断该字段是不是存在
47 if(user.ExtensionData.ContainsKey(SimpleRegistrationFields.Nickname))
48 {
49 nickName=user.ExtensionData[SimpleRegistrationFields.Nickname];
50 }
51 else
52 {
53 nickName=defaultNickName!=null?defaultNickName:account;
54 }
55 AccountHelper.setUserInfo(account, nickName, this.name);
56 AccountHelper.returnOpener();
57 page.Response.End();
58 }
59 break;
60 }
61 }
62 }

7.使用OpenSocial接口登录支持校内网用户的登录

上面的几篇文章之中,我按照顺序讲解了Google的AuthSub,Yahoo的BBAuth,Live的Account ID,OpenID的登录,而这正是我在规划这个Step1账户登录系统时候设计要支持的几个网站,现在已经全部支持了,不过可惜的是这些都是国外的网站,国内的很多用户没有这样的账户的,这样,这个系统的可用性就会大大降低了,可惜国内的大网站们现在似乎都没有要开放API的意思,这一点是比较郁闷的。

我仔细的想过如何才能支持国内的一些网站的登录,结果还是没有什么办法,不过我忽然有一天在上校内网的时候,忽然想到,可不可以使用这些SNS的网站的接口来完成这个功能呢?现在SNS网站开放接口确实是像一阵风一样,包括51.com也开放了API,不过51.com是我比较鄙视的网站,也就不提它了。

我开始仔细的考虑整个登录过程,按照以前的每一个接口的逻辑,首先要生成一个登录网址,这个网址怎么得来呢?可以用我们自己开发的SNS APP的页面地址,这个网址通常是固定的,完全可以使用这个网址来作为我们系统的登录转向地址。

我们将用户转向到这个地址之后,因为SNS网站的内容都是需要用户登录的,所以,用户会被再次转向到SNS的登录界面,这样,用户的登录就开始了。

在用户登录完成之后,就会被重新转向到APP的页面,这样的话,APP就会被加载。

APP加载之后,其任务就是获取到用户的资料(ID和名称),然后带上这些参数,将整个页面转向回到登录系统,这样的话,整个登录过程就完成了。

上面所说的是一个基本原理,如果要确实的完成之一个过程,需要以下以下条件:

1.SNS系统之中,对每一个APP都有一个固定不变的网址,这个条件通常是具备的,因为SNS网站考虑到用户可能通过网址的复制来分享应用,所以这个网址肯定存在;

2.打开这个网址,就会直接转向到登录的页面。要是哪个SNS网站允许未登录用户查看某个APP的简单信息,需要用户手工点击一个“登录”按钮什么的才能进入登录过程,这样的话需要用户多点击一次,就使登录过程不怎么顺畅了;

3.用户登录完成之后,应该回到APP的页面,要是哪个SNS网站在登录之后总是到用户的控制台,而不是返回到登录请求页,那就不行了;

4.加载一个APP最好是在当前页面上加载,而不是在Iframe上加载,假如确实是在Iframe上加载的(目前最多的就是这种模式),也希望是在同一个域,实在不行是同一个根域也行,这是为了能够在获取到用户信息之后让网页的顶端框架跳转到登录系统,如果实在不能通过JS来进行转向,那就只能放target="_top"的链接提供给用户点击,那样的话,登录的流畅性也就大打折扣了。

能满足上面的四个条件,再加上SNS的APP通常都是能够获取用户的资料的,这样就可以比较流畅的融入到Step1登录系统之中,根据我使用的情况,校内网是能够实现比较好的登录过程的,下面我来介绍一下实现的具体过程(校内网有两种接口,我是采用Google提供标准的OpenSocial接口实现的):

1.首先还是要去校内网申请一个应用程序,在"开发者应用"之中点击"申请OpenSocial开发许可证",就会进入开发许可证申请页面:

20081210155738429.gif

下面会逐步的显示实现的代码,因为这个比较特殊,代码分为4块:Web.config配置代码,OpenSocial App页面ASPX,OpenSocial App页面

类,和OpenSocialServer.cs代码

1.Web.Config配置代码:

1 <accountserver name="xiaonei.com" mce_href="http://www.3648.com/" type="Step1.AccountServer.AccountServers.OpenSocialServer,Step1.AccountServer" loginurl="&lt;a href=" http: www.3648.com login.html passport apps.xiaonei.com>http://apps.xiaonei.com/passport/login.html"/&gt;</accountserver>

2.OpenSocial APP页面ASPX,在本系统之中被我部署到http://account.step1.cn/account/tools/opensocial.aspx(直接打开无效)

OpenSocia APP页面代码

1<?xml version="1.0" encoding="UTF-8" ?>
2<module><br>3 <moduleprefs title="Step1 Account"><br>4 <require feature="opensocial-0.8"><br>5 </require></moduleprefs><br>6 <content type="html"><br>7 <!--[CDATA[ <br />8 <br/><br/> <br />9 <center><a id="accountLink" target="_top"><h2>正在登录到<%=domain%></h2></a></center> <br />10<mce:script language="javascript"><!-- <br />11 function request() <br />12 { <br />13 //请求用户信息 <br />14 var idspec = opensocial.newIdSpec({"userId":"OWNER","groupId":"FRIENDS"}); <br />15 var req = opensocial.newDataRequest(); <br />16 req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.OWNER), "get_owner"); <br />17 req.send(response); <br />18 } <br />19 function response(dataResponse) <br />20 { <br />21 //读取用户信息并转向到URL <br />22 var owner = dataResponse.get('get_owner').getData(); <br />23 gotoUrl('<%=handleUrl%>&id='+encodeURIComponent(owner.getId())+'&name='+encodeURIComponent(owner.getDisplayName())); <br />24 <br />25 } <br />26 function gotoUrl(url) <br />27 { //转向到URL <br />28 document.getElementById("accountLink").firstChild.innerHTML="登录完成,请点击返回(5秒之后自动返回)"; <br />29 document.getElementById("accountLink").href=url; <br />30 setTimeout(function(){top.location=url;},5000); <br />31 } <br />32 gadgets.util.registerOnLoadHandler(request); <br />33 // --><br>34 ]]--&gt; <br>35 </content><br>36</module>

3.OpenSocial APP页面代码文件

OpenSocialPage.cs

1 public partial class OpenSocialPage : System.Web.UI.Page
2 {
3 protected string handleUrl,acc,domain;
4 protected void Page_Load(object sender, EventArgs e)
5 {//设置需要显示给APP的变量
6 BaseServer server = AccountHelper.getServerByName(Request["ass"]);
7 handleUrl = server.getHandleUrl();
8 domain = Configuration.Instance().rootUrl;
9 }
10 }

4.OpenSocialServer.cs

OpenSocialServer.cs

1 public class OpenSocialServer:BaseServer
2 {
3 private string loginUrl;
4 //采用Web.Config之中的XML节点作为构造函数参数
5 public OpenSocialServer(System.Xml.XmlNode node)
6 : base(node)
7 {
8 for (int i = 0; i < node.Attributes.Count; i++)
9 {
10 switch (node.Attributes[i].LocalName)
11 {
12 case "loginUrl":
13 loginUrl = node.Attributes[i].Value;
14 break;
15 }
16 }
17 }
18 public override string getLoginUrl()//直接将XML配置之中的登录URL返回
19 {
20 return loginUrl;
21 }
22 public override void parseHandle(HttpContext page)//处理回转请求
23 {
24 System.Collections.Specialized.NameValueCollection request = HttpUtility.ParseQueryString(page.Request.Url.Query, Encoding.UTF8);
25 string id = request["id"];
26 string name = request["name"];
27 AccountHelper.setUserInfo(id, name, this.name);//设置用户的Cookie
28 AccountHelper.returnOpener();//转向到开始请求登录时的页面
29 }
30 }

8. Step1.AccountClient的实现

在前面的7篇文章之中我介绍了Step1.AccountServer的实现,现在,总体的结构和5种用户登录类型都已经介绍完毕,也就是AccountServer都已经介绍完了,原计划还要介绍OAuth的,不过不论是Google 还是Yahoo对OAuth的实现,我最终都没有能够研究成功,都只是完成了登录过程,却没有实现使用登陆后得到的Token向服务器获取到信息,我会在后面的源码之中同样将这些没有研究完成的内容包含在内,感兴趣的用户可以自己去研究了。
今天,我将介绍Step1.AccountClient的逻辑,也就是如何在一个网站应用程序上使用AccountClient来连接AccountServer,每一个网站应用程序只需要引用AccountClient库,就可以实现登录的过程,以及获得用户信息了。
Step1.AccoutClient是一个非常简单的类库,仅仅包含两个类,AccountHelper类(包含一些静态方法,用来提供给Web应用程序使用),和一个AccountPage类(是一个页面的代码,就是包含登录和注销的页面逻辑,这个页面没有任何界面,仅仅在请求的时候进行Cookie的设置和转向)
因为Step1.AccoutClient逻辑比较简单,这篇文章以代码为主。
先看看Web.Config的代码,这个配置的参数因为很少,因此,仅仅在appSettings标签之中加上以下几个参数就可以了:
Web.Config配置代码

ContractedBlock.gifCode

再看看AccountHelper类:
AccountHelper.cs代码

ContractedBlock.gifCode

回传字段的参数名称

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值