RestSharp身份验证器遵循302重定向

4

我希望你能帮忙翻译一下与RestSharp有关的API内容。这个API是受保护的,需要通过重定向请求到登录服务器进行身份验证,获取cookies,然后再重定向回API。很遗憾,我没有对此任何控制权。

因此,请求的顺序如下:

Request                                            Response
---------------------------------------------------------------------------------
1. GET http api server                             302 Found to login server
2. GET https login server                          401 Unauthorized
3. GET https login server with basic credentials   302 Found to api server with cookies
4. GET http api server with cookies                200 OK

我正在尝试使用RestSharp来实现这个功能。以下是我的代码:
var client = new RestClient("api server")
{
    Authenticator = new HttpBasicAuthenticator("username", "password")
};
var request = new RestRequest("api path", Method.GET);
var result = client.Execute<TResult>(request).Data;

授权标头仅在第一次请求时发送。它不会跟随任何重定向。

有没有办法让RestSharp仅将凭据发送到登录服务器?


我公司的私有API。 - Markus
你是如何验证基本身份验证标头未被发送的?你确定这是否使用基本身份验证?如果涉及基于Web的表单和Cookie,则几乎肯定不是基本身份验证。 - Todd Menier
使用 Fiddler,我可以确认授权头未被发送。我知道该 API 使用基本身份验证。我已经构建了其他非 .NET 应用程序来使用此 API。 - Markus
2个回答

5

发帖到Stack Overflow后,你总是能找到解决方案。

https://github.com/restsharp/RestSharp/issues/414

不要在RestClient上使用IAuthenticator,而是要构建一个自定义的System.Net.IAuthenticationModule。这是我的解决方案:

我的RestSharp认证器

public class MyAuthenticator : IAuthenticator
{
    private readonly CredentialCache _credentials = new CredentialCache();

    public MyAuthenticator(Uri loginServerUrl, string username, string password)
    {
        if (loginServerUrl == null)
        {
            throw new ArgumentNullException("loginServerUrl");
        }

        __registerAuthenticationModule(loginServerUrl);
        _credentials.Add(loginServerUrl, MyAuthenticationModule.TheAuthenticationType, new NetworkCredential(username, password, loginServerUrl.Host));
    }

    private static MyAuthenticationModule __registerAuthenticationModule(Uri loginServerUrl)
    {
        IEnumerator registeredModules = AuthenticationManager.RegisteredModules;
        MyAuthenticationModule authenticationModule;

        while (registeredModules.MoveNext())
        {
            object current = registeredModules.Current;
            if (current is MyAuthenticationModule)
            {
                authenticationModule = (MyAuthenticationModule)current;
                if (authenticationModule.LoginServerUrl.Equals(loginServerUrl))
                {
                    return authenticationModule;
                }
            }
        }

        authenticationModule = new MyAuthenticationModule(loginServerUrl);
        AuthenticationManager.Register(authenticationModule);
        return authenticationModule;
    }

    public void Authenticate(IRestClient client, IRestRequest request)
    {
        request.Credentials = _credentials;
    }
}

My .NET Authentication Module

public class MyAuthenticationModule : IAuthenticationModule
{
    internal const string TheAuthenticationType = "MyAuthentication";

    private readonly CredentialCache _credentialCache = new CredentialCache();
    private readonly Uri _loginServerUrl;

    internal CredentialCache CredentialCache
    {
        get
        {
            return _credentialCache;
        }
    }

    internal Uri LoginServerUrl
    {
        get
        {
            return _loginServerUrl;
        }
    }

    internal MyAuthenticationModule(Uri loginServerurl)
    {
        if (loginServerurl == null)
        {
            throw new ArgumentNullException("loginServerUrl");
        }

        _loginServerUrl = loginServerurl;
    }

    public Authorization Authenticate(string challenge, WebRequest request, ICredentials credentials)
    {
        Authorization result = null;

        if (request == null || credentials == null)
        {
            result = null;
        }

        else
        {
            NetworkCredential creds = credentials.GetCredential(LoginServerUrl, TheAuthenticationType);
            if (creds == null)
            {
                return null;
            }

            ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
            if (policy != null && !policy.ShouldSendCredential(LoginServerUrl, request, creds, this))
            {
                return null;
            }

            string token = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", creds.UserName, creds.Password)));

            result = new Authorization(string.Format("Basic {0}", token));
        }

        return result;
    }

    public string AuthenticationType
    {
        get { return TheAuthenticationType; }
    }

    public bool CanPreAuthenticate
    {
        get { return false; }
    }

    public Authorization PreAuthenticate(WebRequest request, ICredentials credentials)
    {
        return null;
    }
}

像这样将其添加到 RestSharp 客户端

var client = new RestClient(commonApiUrl)
{
    Authenticator = new MyAuthenticator(loginServerUrl, username, password)
};

我在 MyAuthenticationModule.Authenticate 方法上设置了断点,但在我的情况下它从未被触发。 在我的情况下,登录网址与api网址相同, /wp-json/wp/v2/users/me 需要基本的授权,然后在成功授权后将我重定向到 /wp-json/wp/v2/users/2,该网址也需要基本授权。 - steakoverflow
Vani,你可能需要为你的RestClient添加一个System.Net.CookieContainer。请查看https://github.com/restsharp/RestSharp/wiki/Cookies。 - Markus
但在我的情况下,它甚至没有验证第一个url (/wp-json/wp/v2/users/me),现在它在第一个url上返回401。当使用基本身份验证器时,它确实验证了第一个url并302到第二个url。然后它在403处中断了。 - steakoverflow
啊,好的。我从未尝试过在同一域内进行此操作。你传递给MyAuthenticator构造函数的是什么?loginServerUrl是否与您传递给RestClient构造函数的URL相同? - Markus
是的,它是相同的URL。在我使用第一个URL进行身份验证之前,我不知道第二个URL会是什么。 - steakoverflow

1
您可以在Authenticate方法中将CredentialsCache对象分配给请求。 将这些凭据传递给请求表明您允许它们被使用,即使是对于后续请求(重定向)也是如此。
从这里 msdn article可以了解到:

当重定向时,CredentialCache不会从Credentials属性中删除,因为WebRequest知道您允许发送凭据的位置。 您还可以通过将其分配给后续请求来重用缓存。

因此,RestSharp BasicAuthenticator的实现可能如下所示:
 public class BasicAuthenticator : IAuthenticator
    {
        private readonly string _baseUrl;
        private readonly string _userName;
        private readonly string _password;
        private readonly CredentialCache _credentialCache;

        public BasicAuthenticator(string baseUrl, string userName, string password)
        {
            _baseUrl = baseUrl;
            _userName = userName;
            _password = password;

            _credentialCache = new CredentialCache
            {
                {new Uri(_baseUrl), "Basic", new NetworkCredential(_userName, _password)}
            };
        }

        public void Authenticate(IRestClient client, IRestRequest request)
        {
            request.Credentials = _credentialCache;

            if (request.Parameters.Any(parameter =>
                            parameter.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)))
            {
                return;
            }
            request.AddParameter("Authorization", GetBasicAuthHeaderValue(), ParameterType.HttpHeader);
        }


        private string GetBasicAuthHeaderValue()
        {
            return string.Format("Basic {0}",
                            Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}",
                                _userName, _password))));
        }
    }

完美运行! - Fidel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接