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