关于微博服务端API的OAuth认证实现

      新浪微博跟update相关的api已经挂了很多天了一直没有恢复正常,返回错误:40070 Error limited application access api!,新浪开放平台的论坛里n多的人都在等这个恢复,新浪官方也相当的恶心出问题了连个公告都没有,既不说什么原因又不说什么时候能恢复,。还是有版主说是api正在升级礼拜1恢复正常今天都礼拜2了还是不行。基于这个原因我的android版的新浪微博客户端已经停工好几天了,刚好是跟update相关的一些功能。

     客户端开发不成了,就自己做做服务端程序,提供类似新浪微博rest api服务, api其实说简单也很简单了,无法是通过链接对外提供json或者xml格式的数据和接收外部提供的数据进去相应的存储、删除、更新等操作。过程中碰到的最麻烦的问题就是OAuth认证功能了,在做android版的新浪微博客户端时候也花了蛮长的时间对OAuth认证进行研究,在客户端原先是采用oauth-signpost开源项目,后来由于某些原因就放弃了这个开源类库,自己重新写了OAuth认证部分的实现, 现在做服务端的OAuth认证,其实有过做客户端的经验做服务端也差不多,简单的说无非是客户端对参数字符串进行签名然后把签名值传输到服务端,服务端也对同样对参数字符串进行签名,把从客户端传过来的签名值进去比较,简单的说就这么个过程,具体实现肯定比这个要复杂多了,不明真相的同学可以google一下OAuth进行深入的学习研究了。

      服务端程序用asp.net和C#编写了而非java,理由很简单本人对.net更加熟悉。由于想快速的实现效果采用了oauth-dot-net开源项目并没有全部自己写。

      一、首先新建名为Rest Api的ASP.NET Web应用程序,然后添加 oauth-dot-net开源项目相关的几个dll(Castle.Core.dll、Castle.MicroKernel.dll、Castle.Windsor.dll、CommonServiceLocator.WindsorAdapter.dll、Microsoft.Practices.ServiceLocation.dll、OAuth.Net.Common.dll、OAuth.Net.Components.dll、OAuth.Net.ServiceProvider.dll)。

     二、在Web.config文件里添加相应的配置,具体可以参考OAuth.Net.Examples.EchoServiceProvider项目,然后在Global.asax.cs添加如下代码:

public override void Init()
        {
            IServiceLocator injector 
=
                
new WindsorServiceLocator(
                    
new WindsorContainer(
                        
new XmlInterpreter(
                            
new ConfigResource("oauth.net.components"))));

            ServiceLocator.SetLocatorProvider(() 
=> injector);

        }

      接下来是比较重要,就是request_token、authorize、access_token的实现,OAuth认证实现的几个过程,不理解可以看android开发我的新浪微博客户端-OAuth篇(2.1) ,具体代码实现很多是参考OAuth.Net.Examples.EchoServiceProvider示例项目。

      三、 首先新建ConsumerStore.cs类,用来存储Consumer信息,由于测试项目所以存储在内存中并没有考虑保存到数据库,真实项目的时候请把相应的Consumer信息保存到数据库中。Consumer信息对应新浪微博其实就是应用的App Key和App Secret,当开发者在新浪微博建一个新的应用获取App Key和App Secret,所以完整的应该还需要一个开发一个提供给第三方开发者申请获取App Key和App Secret的功能页面,这里就不具体实现,直接在代码里写死了一个名为测试应用的Consumer,App Key:2433927322,App Secret:87f042c9e8183cbde0f005a00db1529f,这个提供给客户端测试用。 具体代码如下:

public sealed class ConsumerStore : InMemoryConsumerStore, IConsumerStore
    {
        
internal static readonly IConsumer FixedConsumer = new OAuthConsumer("2433927322""87f042c9e8183cbde0f005a00db1529f""测试应用", ConsumerStatus.Valid);

        
public ConsumerStore()
        {
            
this.ConsumerDictionary.Add(
                ConsumerStore.FixedConsumer.Key, 
                ConsumerStore.FixedConsumer);
        }

        
public override bool Add(IConsumer consumer)
        {
            
throw new NotSupportedException("Consumers cannot be added to this store--it is fixed.");
        }

        
public override bool Contains(string consumerKey)
        {
            
return ConsumerStore.FixedConsumer.Key.Equals(consumerKey);
        }

        
public override bool Update(IConsumer consumer)
        {
            
throw new NotSupportedException("Consumers cannot be updated in this store--it is fixed.");
        }

        
public override bool Remove(IConsumer consumer)
        {
            
throw new NotSupportedException("Consumers cannot be removed from this store--it is fixed.");
        }

    } 

      四、接下来就是request_token功能,新建RequestTokenHandler.cs ,这个是OAuth.Net.ServiceProvider.RequestTokenHandler子类,并且是httpHandlers所以需要在Web.config中添加httpHandlers配置,这个用来接收客户端程序的请求,返回给客户端程序Request Token和Request Secret用,具体代码如下:

public sealed class RequestTokenHandler : OAuth.Net.ServiceProvider.RequestTokenHandler
    {   
        
protected override void IssueRequestToken(HttpContext httpContext, OAuthRequestContext requestContext)
        {
            
//产生RequestToken
            IRequestToken token = this.GenerateRequestToken(httpContext, requestContext);

            requestContext.RequestToken 
= token;
            Uri callbackUri;
            
if (Uri.TryCreate(requestContext.Parameters.Callback, UriKind.Absolute, out callbackUri))
            {
                
if (!ServiceProviderContext.CallbackStore.ContainsCallback(token))
                {
                    
//保存Callback地址了
                    ServiceProviderContext.CallbackStore.AddCallback(token, callbackUri);
                }
            }
            
else
                OAuthRequestException.ThrowParametersRejected(
new string[] { Constants.CallbackParameter }, "Not a valid Uri.");


            
//把token.Token和token.Secret输出到客户端,
            requestContext.ResponseParameters[Constants.TokenParameter] = token.Token;
            requestContext.ResponseParameters[Constants.TokenSecretParameter] 
= token.Secret;
        }

        
protected override IRequestToken GenerateRequestToken(HttpContext httpContext, OAuthRequestContext requestContext)
        {
            
            
return ServiceProviderContext.TokenGenerator.CreateRequestToken(requestContext.Consumer, requestContext.Parameters);
        }

    } 

      五、 接着是authorize功能,新建名为authorize.aspx的页面,用来给用户输入账号和密码进行授权的页面,这个页面很简单具体如下图,在这个页面中获取用户输入的账户和密码跟数据库中存储的用户账号和密码进行验证,如果验证通过返回之前客户端提供的callback地址,并且给这个地址添加一个校验码,具体代码如下:

public partial class authorize : System.Web.UI.Page
    {
        
protected void Page_Load(object sender, EventArgs e)
        {

        }

        
protected void Button1_Click(object sender, EventArgs e)
        {
            
if (loginName.Text == "test" && password.Text == "123")
            {
                
string toke = Request.Params["oauth_token"];
                IRequestToken tk 
= ServiceProviderContext.TokenStore.GetRequestToken(toke);
                Uri callback 
= ServiceProviderContext.CallbackStore.GetCalback(tk);
                
string oauth_verifier = ServiceProviderContext.VerificationProvider.Generate(tk);
                Response.Redirect(callback.ToString() 
+ "?oauth_verifier=" + oauth_verifier);
            }
            
        }

    } 

     六、接下来就是access_token功能,新建AccessTokenHandler.cs , 这个是OAuth.Net.ServiceProvider.AccessTokenHandler子类,并且是httpHandlers所以需要在Web.config中添加httpHandlers配置,这个用来接收客户端程序的请求,返回给客户端程序Access Token和Access Secret用,具体代码如下:

public sealed class AccessTokenHandler : OAuth.Net.ServiceProvider.AccessTokenHandler
    {
        
protected override void IssueAccessToken(HttpContext httpContext, OAuthRequestContext requestContext)
        {
            
//产生access token
            IAccessToken accessToken = this.GenerateAccessToken(httpContext, requestContext);

            accessToken.Status 
= TokenStatus.Authorized;

            
// 把accessToken和accessSecret输出到客户端,
            requestContext.ResponseParameters[Constants.TokenParameter] = accessToken.Token;
            requestContext.ResponseParameters[Constants.TokenSecretParameter] 
= accessToken.Secret;
        }

        
protected override IAccessToken GenerateAccessToken(HttpContext httpContext,  OAuthRequestContext requestContext)
        {
            
return ServiceProviderContext.TokenGenerator.CreateAccessToken(requestContext.Consumer, requestContext.RequestToken);
        }
    }

public class TokenGenerator : ITokenGenerator
    {
        
internal static readonly IRequestToken FixedRequestToken = new OAuthRequestToken("requestkey",
            
"requestsecret",
            ConsumerStore.FixedConsumer,
            TokenStatus.Authorized,
            
null,
            ServiceProviderContext.DummyIdentity,
            
new string[] { });

        
internal static readonly IAccessToken FixedAccessToken = new OAuthAccessToken(
            
"accesskey",
            
"accesssecret",
            ConsumerStore.FixedConsumer,
            TokenStatus.Authorized,
            TokenGenerator.FixedRequestToken);

        
public IRequestToken CreateRequestToken(IConsumer consumer, OAuthParameters parameters)
        {
            
return TokenGenerator.FixedRequestToken;
        }

        
public IAccessToken CreateAccessToken(IConsumer consumer, IRequestToken requestToken)
        {
            
return TokenGenerator.FixedAccessToken;
        }
    }

public class TokenStore : InMemoryTokenStore, ITokenStore
    {
        
public TokenStore()
        {
            
this.RequestTokenDictionary.Add(
                TokenGenerator.FixedRequestToken.Token,
                TokenGenerator.FixedRequestToken);

            
this.AccessTokenDictionary.Add(
                TokenGenerator.FixedAccessToken.Token,
                TokenGenerator.FixedAccessToken);
        }

        
public override bool Add(IRequestToken token)
        {
            
throw new NotSupportedException("Tokens cannot be added to the token store--it is fixed.");
        }

        
public override bool Add(IAccessToken token)
        {
            
throw new NotSupportedException("Tokens cannot be added to the token store--it is fixed.");
        }

        
public override bool Update(IRequestToken token)
        {
            
throw new NotSupportedException("Tokens cannot be updated in the token store--it is fixed.");
        }

        
public override bool Update(IAccessToken token)
        {
            
throw new NotSupportedException("Tokens cannot be updated in the token store--it is fixed.");
        }

        
public override bool Remove(IRequestToken token)
        {
            
throw new NotSupportedException("Tokens cannot be removed from the token store--it is fixed.");
        }

        
public override bool Remove(IAccessToken token)
        {
            
throw new NotSupportedException("Tokens cannot be removed from the token store--it is fixed.");
        }

    } 

 这样就完成了一个最最简单小型的服务端OAuth认证,然后用android客户端进行测试ok通过。

     注意点:

     一、android模拟器访问本地服务地址为10.0.2.2,比如http://localhost:3423/authorize.aspx在模拟器中用http://10.0.2.2:3423/authorize.aspx。

     二、OAuth.Net类库的OAuth.Net.Common项目中的interface ICallbackStore 添加了一个Uri GetCalback(IRequestToken token);并且在具体的实现类InMemoryCallbackStore添加了实习代码:

          public Uri GetCalback(IRequestToken token)

        {
            lock (this.callbackStore)
            {
                if (this.callbackStore.ContainsKey(token))
                {
                    return this.callbackStore[token];
                }
                else
                {
                    return null;
                }
            }
        }

       三、为了能用我前面做的给新浪用的android客户端,对于类库源代码AccessTokenHandler的ParseParameters方法做了如下修改,因为新浪请求api的时候都会加一个source的参数:

            protected virtual void ParseParameters(HttpContext httpContext, OAuthRequestContext requestContext)

        {
            .......
            parameters.AllowOnly(
                    Constants.ConsumerKeyParameter,
                    Constants.TokenParameter,
                    Constants.SignatureMethodParameter,
                    Constants.SignatureParameter,
                    Constants.TimestampParameter,
                    Constants.NonceParameter,
                    Constants.VerifierParameter,
                    Constants.VersionParameter, // (optional)
                    Constants.RealmParameter, // (optional)
                    "source");
            ......
        }
 

 

 

posted @ 2011-03-01 20:09  水的右边  阅读(13238)  评论(51编辑  收藏  举报