将OAuthWebSecurity与Salesforce一起使用

时间:2013-10-29 20:29:52

标签: asp.net-mvc-4 oauth-2.0 salesforce single-sign-on

我正在尝试让ASP.NET MVC网站接受Salesforce作为身份验证提供商,但我没有运气。我将从目前为止的IAuthenticationClient开始:

public class SalesForceOAuth2Client : OAuth2Client
{
    private readonly String consumerKey;
    private readonly String consumerSecret;

    #if DEBUG
        private const String BaseEndpoint = @"https://test.salesforce.com";
    #else
        private const String BaseEndpoint = @"https://login.salesforce.com";
    #endif

    private const String AuthorizeEndpoint = BaseEndpoint + @"/services/oauth2/authorize";
    private const String TokenEndpoint = BaseEndpoint + @"/services/oauth2/token";
    private const String RevokeEndpoint = BaseEndpoint + @"/services/oauth2/revoke";

    public SalesForceOAuth2Client(String consumerKey, String consumerSecret)
        : base("SalesForce")
    {
        if (String.IsNullOrWhiteSpace(consumerKey))
        {
            throw new ArgumentNullException("consumerKey");
        }
        if (String.IsNullOrWhiteSpace(consumerSecret))
        {
            throw new ArgumentNullException("consumerSecret");
        }
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
    }

    protected override Uri GetServiceLoginUrl(Uri returnUrl)
    {
        String redirect_url = returnUrl.AbsoluteUri;

        // Hack to work-around the __provider__ & __sid__ query parameters,
        // but it is ultimately useless.
        /*String state = String.Empty;
        Int32 q = redirect_url.IndexOf('?');
        if (q != -1)
        {
            state = redirect_url.Substring(q + 1);
            redirect_url = redirect_url.Substring(0, q);
        }*/

        var builder = new UriBuilder(AuthorizeEndpoint);
        builder.Query = "response_type=code"
                      + "&client_id=" + HttpUtility.UrlEncode(this.consumerKey)
                      + "&scope=full"
                      + "&redirect_uri=" + HttpUtility.UrlEncode(redirect_url)
                      // Part of the above hack (tried to use `state` parameter)
                      /*+ (!String.IsNullOrWhiteSpace(state) ? "&state=" + HttpUtility.UrlEncode(state) : String.Empty)*/;
        return builder.Uri;
    }

    protected override IDictionary<String, String> GetUserData(String accessToken)
    {
        // I am not sure how to get this yet as everything concrete I've
        // seen uses the service's getUserInfo call (but this service relies
        // heavily on a username, password, token combination. The whole point
        // of using oatuh is to avoid asking the user for his/her credentials)
        // more information about the original call:
        // http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getuserinfo.htm

        // Return static information for now
        //TODO: Get information dynamically
        return new Dictionary<String, String>
        {
            { "username", "BradChristie" },
            { "name", "Brad Christie" }
        };
    }

    protected override String QueryAccessToken(Uri returnUrl, String authorizationCode)
    {
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
        webRequest.ContentType = "application/x-www-form-urlencoded";
        webRequest.Method = "POST";
        using (StreamWriter streamWriter = new StreamWriter(webRequest.GetRequestStream()))
        {
            streamWriter.Write("grant_type=authorization_code");
            streamWriter.Write("&client_id=" + HttpUtility.UrlEncode(this.consumerKey));
            streamWriter.Write("&client_secret=" + HttpUtility.UrlEncode(this.consumerSecret));
            streamWriter.Write("&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.AbsoluteUri));
            streamWriter.Write("&code=" + HttpUtility.UrlEncode(authorizationCode));
            streamWriter.Flush();
        }

        HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
        if (webResponse.StatusCode == HttpStatusCode.OK)
        {
            using (StreamReader streamReader = new StreamReader(webResponse.GetResponseStream()))
            {
                String response = streamReader.ReadToEnd();
                var queryString = HttpUtility.ParseQueryString(response);
                return queryString["access_token"];
            }
        }
        return String.Empty;
    }
}

主要问题是redirect_uri!= Callback Url

Salesforce会强制执行您在应用程序配置中提供的回调URL,以使完全redirect_uri QueryAccessToken中提供的值相匹配。很遗憾,OAuthWebSecurity依赖于DotNetOpenAuth.AspNet,该库会附加两个查询参数:__provider____sid__。如果我尝试删除它们(请参阅GetServiceLoginUrl中的hack),显然登录失败是因为回拨不知道如何继续处理请求而不知道要使用哪个提供程序。

为了解决这个问题,我注意到request调用接受了一个可选的state参数,该参数(基本上)用于在请求/回调中来回传递信息。但是,依赖__provider____sid__作为data=__provider__%3DSalesForce%26__sid__%3D1234567890的自己的密钥是没用的。

是否有解决办法,无需分叉/重新编译Microsoft.Web.WebPages.OAuth库并修改OAuthWebSecurity.VerifyAuthenticationCore(HttpContextBase, String)方法以首先查看data,然后继续OpenAuthSecurityMananer.GetProviderName

此外,如果注册重要(AuthConfig.cs):

OAuthWebSecurity.RegisterClient(
  new SalesForceOAuth2Client(/*consumerKey*/, /*consumerSecret*/),
  "SalesForce",
  new Dictionary<String, Object>()
);

更新(2013年1月11日)

我刚收到Salesforce的回复。看起来他们不知道如何实现RFC的3.1.2,这意味着您使用return_uri发送的任何查询参数不仅被忽略,而且被禁止(至少在动态性质时) 。所以,看起来我不能使用在所有其他平台上工作的库并遵循标准 - 我必须自己创建。

叹息。

0 个答案:

没有答案