2011年12月2日

asp.net实现OAuth认证服务端接口

作者 非鱼

现在的互联网是开放的互联网,你要想融入这个开放的环境,当然自己也要开放一点。如果你希望别人能够很方便的使用你的网站上的用户及数据,当然要提供比较开放一点的接口。如果能够实现标准的OAuth认证接口,第三方用户在开发的时候就会容易很多,因为他们很可能在开发微博程序的时候已经做过同样的事写过很多这样的代码了。

不过C#实现OAuth认证还真是挺烦的,主要是,类库有好几个,但是,都找不到文档。适用于客户端实现的文档倒是有很多,适用于服务器端的真的没有。连类库的官方文档都找不到,只有源码里仅存的一份简易Test可以作为参考。费了九牛二虎之力终于算是实现了一个几乎一模一样的OAuth认证接口,记录一下。

经过搜索,比较,读资源,读源码,再比较,最后选择了oauth-dot-net这个类库。虽然它已经一年多没更新了,曾经承诺的2.0接口也没有动静,但是,它的Server端实现算是最容易理解和操作的了。

下载源码也好,Release版本也好,把一堆dll引入到自己的项目中。关于这个项目的类型,直接用VS新建网站即可,不需要web application,也不需要web service,也不需要WCF。但是呢,使用过程中你需要新建几个类,而且这几个类需要在web.config中指定,所以它需要完整的命名空间,简单起见,新建一个类库,把所有的类定义和通用的方法放到这里面,当然,因为我们的项目原本就有一个用来定义类变量和公共方法的dll,就直接放一起了。

然后你需要实现几个东西:ConsumerStore,CallbackStore,TokenGenerator,TokenStore,它们分别实现自己的名字前面加上I的那个接口。具体如何实现以及每个函数的作用,可以参考源码中提供的InMemoryXXX的实现方式。具体来讲,consumer是一个个应用,或者第三方的接入网站,每个应用有个唯一的appkey和appsecret,跟新浪的应用一样,你需要在这儿实现根据appkey查找该consumer的功能,每次请求发起,都需要调用。因为创建应用一般来说有自己的流程,所以在这个store可以不用管它。callback是调用者的返回URL,在申请request_token的时候会传递过来,需要保存下来到用户亲自授权完成以后再读出来进行跳转。TokenGenerator只有两个函数,用来生成新的request_token和access_token,如何生成它里面的key和secret,随便你自己,只要永远不重复就可以了,嫌麻烦就直接用GUID。TokenStore是用来查询token的,比如根据tokenkey查找request_token或者access_token,你可以用自己的数据库来实现,字段也就那几个,也是以后每个请求都要读取的。但是它自己的实现接口里面忽略了很重要的一点,就是没有把access_token跟用户Id绑定起来,这个一定要自己实现的,不然程序传一个access_token过来,你找不到是哪个合法用户。

实现了这几个类以后,在源码里面找到OAuth.Net.Examples.EchoServiceProvider这个示例项目,把它的web.config里面的oauth.net.serviceprovider和oauth.net.components两段配置信息拷到自己的web.config里面来,然后修改oauth.net.components这一段里面对应的四个自己已经实现的类,指向自己的类库的位置。

然后添加一个global.asax,对于网站项目,这个文件是没有codefile文件的,可以直接在asax下面写代码,把示例项目中的那个Init方法复制过来。这时候,基础工作就完成了。

然后需要实现三个过程,申请request_token(这一步没有界面,对方直接用代码读你的URL获取这个信息),用户跳到authorize的页面,输入用户名和密码,验证并授权给该客户端,验证完成后返回用户指定的callbackurl,用户再拿该返回链接中的参数申请access_token(这一步也没有界面,通过程序间完成),保存下你返回的token和secret就可以用在其它所有的读取数据的请求上了。

示例中已经给你准备好了一个RequestTokenHandler.cs和AccessTokenHandler.cs,不过这两个示例除了把你搞哭没啥大作用。

新建一个request_token.ashx文件,像RequestTokenHandler.cs一样继承OAuth.Net.ServiceProvider.RequestTokenHandler这个类,实现里面的IssueRequestToken方法,这个时候,建议你就不要再参考示例项目里面的这个代码了,而去OAuth.Net.ServiceProvider.RequestTokenHandler这个类里面,参考它的IssueRequestToken方法。因为示例项目里面的token和secret都是写死的(包括consumer也是写死的),因此它没有对生成的token做任何保存的动作。实际上,你需要将这个token保存到数据库里,同时保存callbackurl,至于要调用CallbackStore来保存还是你直接操作自己的数据库字段,无所谓。这个文件最后是把生成的token和secret以纯文本的形式返回给客户端。

然后客户端把得到的token字符串作为参数,跳到你的authorize.aspx页面,这里你需要提供用户的登录框,和一个授权按钮。当用户点下授权按钮的时候,检查用户名密码是否正确,然后根据用户跳转过来的时候传过来的那个oauth_token参数,找到你前面生成的request_token对应的其它字段(如果你要对request_token设置有效时间,这里需要判断该时间),读出其中的callbackurl字段,然后,调用ServiceProviderContext.VerificationProvider.Generate()方法根据该request_token生成一个oauth_verifier字符串,将该值附加到callbackurl的后面,跳转过去。但是在跳转过去之前,你必须做一件事。

用户的callbackurl页面收到你的oauth_verifier以后,再连同前面的request_token一起来访问你的access_token.ashx,申请access_t0ken。所以用户端也必须保存你前面生成的request_token才可以(临时性的,每次授权前读取,申请到access_token就作废,所以一般用session来保存就行了)。对于access_token.ashx的内容,源码同样提供了一个例子,不过同样是个一点用都没有的例子,因为它完全没有涉及到授权和生成全局唯一access_token的过程。你可以让你的access_token的handler继承自OAuth.Net.ServiceProvider.AccessTokenHandler,然后重写IssueAccessToken方法。这个方法其实很简单,因为父类已经帮你检测了传过来的所有的参数以及签名的正确性,甚至连access_token和verifier是否匹配都验证过了,所以你要做的就是把对应的access_token返回给用户。你可以生成一个新的access_token返回给用户,因为它里面包含的token和secret其实就是两个普通的字符串,跟前面用到的所有的变量都没有直接关系,你只要保存token是唯一的就可以了,因为以后用户做所有的请求,实际都只使用这个token字符串作为自己的唯一身份标志。所以,理所当然,你要把生成的这个access_token永久性的保存到自己的数据库里,还要把这个token跟进行授权的用户的id关联起来,以便以后每次可以查询到这个用户的身份,才能传递他的数据回去。

等等,客户端在申请access_token的时候,只传递了request_token和一个verifier的字符串过来,授权的用户的信息并没有再传回来,你如何知道把哪一个用户关联到这个access_token上?

所以,你需要把创建新access_token的过程提前,放到authorize的时候,在跳转回callbackurl之前,直接在这里创建一个新的access_token,将它和你的request_token还有你的用户的信息关联起来,保存到数据库里。然后在用户申请access_token的时候,你只要根据传递过来的request_token去数据库里找到前面生成的access_token,把两个值返回给客户端就OK了。

现在,认证过程已经完成了,用户可以使用你的数据接口了。

示例中也为你准备了一个echoHandler的测试代码,但是,悲剧的是,这个代码并不包含任何身份验证的过程,是个完全干净的独立的handler,不知道开发团队提供这样一个示例代码的意义何在?

你可以自己新建一个echoHandler.ashx(或者随便什么名字),你需要以前面的OAuth.Net.ServiceProvider.AccessTokenHandler作为模板开始你的工作。将它的代码完整的复制到你的handler文件里面来,慢慢修改。首先,ParseParamters里,检查参数完整性,把VerifierParameter删掉,同样的把this.CheckVerifier(context, requestContext);也删掉,verifier参数只在申请access_token的时候才会出现。然后把parameters.AllowOnly检查过程删掉,原因很简单。然后修改SetRequestToken函数,它的代码是通过token参数找到request_token对象,把它改成通过token参数读取你的数据库里的access_token对象(当然,同时把它关联的用户的信息也读出来),然后,把IssueAccessToken函数的过程改成你的业务处理过程,比如输出个用户详细信息啥的。至于输出成json还是xml,就看你的个人喜好和用户的要求了。每个业务处理的handler的返回格式实际是无固定格式的,因为这毕竟是跟你自己的网站业务紧密相关的。

这个时候,你的网站的标准的RESTful格式的api接口就准备好了,如果用户有开发新浪微博客户端的经验,同样的代码拿过来稍微改改就能完成你的网站的OAuth认证,然后就可以方便的调用你的其它的数据接口了。(不过新浪现在已经开始推广OAuth2.0接口,而oauth-dot-net类库目前还没有提供2.0相关的验证方法。如果你有兴趣,可以自己实现一下2.0的交换参数的过程,然后,告诉我一下。