Java 是否可以在没有重定向服务器的情况下使用 OAuth 2.0?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38268175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:09:43  来源:igfitidea点击:

Is it possible to use OAuth 2.0 without a redirect server?

javaoauth-2.0surveymonkey

提问by Tovi7

I'm trying to create a local Java-based client that interacts with the SurveyMonkey API.

我正在尝试创建一个与 SurveyMonkey API 交互的本地基于 Java 的客户端。

SurveyMonkey requires a long-lived access token using OAuth 2.0, which I'm not very familiar with.

SurveyMonkey 需要使用 OAuth 2.0 的长期访问令牌,我对此不太熟悉。

I've been googling this for hours, and I think the answer is no, but I just want to be sure:

我已经用谷歌搜索了几个小时,我认为答案是否定的,但我只想确定:

Is it possible for me to write a simple Java client that interacts with the SurveyMonkey, without setting up my own redirect server in some cloud?

我是否可以编写一个与 SurveyMonkey 交互的简单 Java 客户端,而无需在某些云中设置我自己的重定向服务器

I feel like having my own online service is mandatory to be able to receive the bearer tokens generated by OAuth 2.0. Is it possible that I can't have SurveyMonkey send bearer tokens directly to my client?

我觉得必须拥有自己的在线服务才能接收 OAuth 2.0 生成的不记名令牌。我是否可能无法让 SurveyMonkey 直接向我的客户发送不记名令牌?

And if I were to set up my own custom Servlet somewhere, and use it as a redirect_uri, then the correct flow would be as follows:

如果我要在某处设置自己的自定义 Servlet,并将其用作 redirect_uri,那么正确的流程如下:

  1. Java-client request bearer token from SurveyMonkey, with redirect_uri being my own custom servlet URL.
  2. SurveyMonkey sends token to my custom servlet URL.
  3. Java-client polls custom servlet URL until a token is available?
  1. 来自 SurveyMonkey 的 Java 客户端请求不记名令牌,redirect_uri 是我自己的自定义 servlet URL。
  2. SurveyMonkey 将令牌发送到我的自定义 servlet URL。
  3. Java 客户端轮询自定义 servlet URL 直到令牌可用?

Is this correct?

这样对吗?

采纳答案by General Kandalaft

Not exactly, the whole point of the OAuth flow is that the user (the client you're accessing the data on behalf of) needs to give you permission to access their data.

不完全是,OAuth 流程的重点在于用户(您代表您访问数据的客户端)需要授予您访问其数据的权限。

See the authentication instructions. You need to send the user to the OAuth authorize page:

请参阅身份验证说明。您需要将用户发送到 OAuth 授权页面:

https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>

This will show a page to the user telling them which parts of their account you are requesting access to (ex. see their surveys, see their responses, etc). Once the user approves that by clicking "Authorize" on that page, SurveyMonkey will automatically go to whatever you set as your redirect URI (make sure the one from the url above matches with what you set in the settings for your app) with the code.

这将向用户显示一个页面,告诉他们您请求访问他们帐户的哪些部分(例如,查看他们的调查、查看他们的回复等)。一旦用户通过单击该页面上的“授权”批准,SurveyMonkey 将自动转到您设置为重定向 URI 的任何内容(确保上面 url 中的那个与您在应用程序设置中设置的内容匹配)和代码.

So if your redirect URL was https://example.com/surveymonkey/oauth, SurveyMonkey will redirect the user to that URL with a code:

因此,如果您的重定向 URL 是https://example.com/surveymonkey/oauth,SurveyMonkey 将使用代码将用户重定向到该 URL:

https://example.com/surveymonkey/oauth?code=<auth_code>

https://example.com/surveymonkey/oauth?code=<auth_code>

You need to take that code and then exchange it for an access token by doing a POST request to https://api.surveymonkey.net/oauth/token?api_key=<your_api_key>with the following post params:

您需要获取该代码,然后通过https://api.surveymonkey.net/oauth/token?api_key=<your_api_key>使用以下 post 参数执行 POST 请求来将其交换为访问令牌:

client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code

This will return an access token, you can then use that access token to access data on the user's account. You don't give the access token to the user it's for you to use to access the user's account. No need for polling or anything.

这将返回一个访问令牌,然后您可以使用该访问令牌访问用户帐户上的数据。您不会将访问令牌提供给供您用于访问用户帐户的用户。不需要投票或任何东西。

If you're just accessing your own account, you can use the access token provided in the settings page of your app. Otherwise there's no way to get an access token for a user without setting up your own redirect server (unless all the users are in the same group as you, i.e. multiple users under the same account; but I won't get into that). SurveyMonkey needs a place to send you the code once the user authorizes, you can't just request one.

如果您只是访问自己的帐户,则可以使用应用设置页面中提供的访问令牌。否则,如果不设置您自己的重定向服务器,就无法为用户获取访问令牌(除非所有用户都与您在同一个组中,即同一帐户下的多个用户;但我不会涉及)。一旦用户授权,SurveyMonkey 需要一个地方向您发送代码,您不能只请求一个。

回答by Florent Morselli

Yes, it is possible to use OAuth2 without a callback URL. The RFC6749introduces several flows. The Implicitand Authorization Codegrant types require a redirect URI. However the Resource Owner Password Credentialsgrant type does not.

是的,可以在没有回调 URL 的情况下使用 OAuth2。在RFC6749介绍了几种流动。该授权码补助类型需要重定向URI。但是,资源所有者密码凭据授予类型没有。

Since RFC6749, other specifications have been issued that does not require any redirect URI:

自 RFC6749 以来,已经发布了其他不需要任何重定向 URI 的规范:

  • RFC7522: Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants
  • RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
  • RFC7522:用于 OAuth 2.0 客户端身份验证和授权授权的安全断言标记语言 (SAML) 2.0 配置文件
  • RFC7523:用于 OAuth 2.0 客户端身份验证和授权授权的 JSON Web 令牌 (JWT) 配置文件

There is another IETF draft that tries to introduce another grant type for limited devices (https://tools.ietf.org/html/draft-ietf-oauth-device-flow) which does not require any redirect URI.

还有另一个 IETF 草案试图为受限设备引入另一种授权类型 ( https://tools.ietf.org/html/draft-ietf-oauth-device-flow),它不需要任何重定向 URI。

In any case, if the grant types above do not fit on your needs, nothing prevent you from creating a custom grant type.

在任何情况下,如果上述授权类型不适合您的需要,没有什么可以阻止您创建自定义授权类型

回答by manos

You doneed to implement something that will act as the redirect_uri, which does not necessarily need to be hosted somewhere else than your client (as you say, in some cloud).

确实需要实现一些将作为 redirect_uri 的东西,它不一定需要托管在您的客户端以外的其他地方(如您所说,在某些云中)。

I am not very familiar with Java and Servelets, but if I assume correctly, it would be something that could handle http://localhost:some_port. In that case, the flow that you describe is correct.

我对 Java 和 Servelets 不是很熟悉,但如果我假设正确,它将是可以处理http://localhost:some_port 的东西。在这种情况下,您描述的流程是正确的。

I implemented the same flow successfully in C#. Here is the class that implements that flow. I hope it helps.

我在 C# 中成功实现了相同的流程。这是实现该流程的类。我希望它有帮助。

class OAuth2Negotiator
{
    private HttpListener _listener = null;
    private string _accessToken = null;
    private string _errorResult = null;
    private string _apiKey = null;
    private string _clientSecret = null;
    private string _redirectUri = null;

    public OAuth2Negotiator(string apiKey, string address, string clientSecret)
    {
        _apiKey = apiKey;
        _redirectUri = address.TrimEnd('/');
        _clientSecret = clientSecret;

        _listener = new HttpListener();
        _listener.Prefixes.Add(address + "/");
        _listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
    }

    public string GetToken()
    {
        var url = string.Format(@"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
                HttpUtility.UrlEncode(@"http://localhost:60403"));
        System.Diagnostics.Process.Start(url);

        _listener.Start();
        AsyncContext.Run(() => ListenLoop(_listener));
        _listener.Stop();

        if (!string.IsNullOrEmpty(_errorResult))
            throw new Exception(_errorResult);
        return _accessToken;
    }

    private async void ListenLoop(HttpListener listener)
    {
        while (true)
        {
            var context = await listener.GetContextAsync();
            var query = context.Request.QueryString;
            if (context.Request.Url.ToString().EndsWith("favicon.ico"))
            {
                context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                context.Response.Close();
            }
            else if (query != null && query.Count > 0)
            {
                if (!string.IsNullOrEmpty(query["code"]))
                {
                    _accessToken = await SendCodeAsync(query["code"]);
                    break;
                }
                else if (!string.IsNullOrEmpty(query["error"]))
                {
                    _errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
                    break;
                }
            }
        }
    }

    private async Task<string> SendCodeAsync(string code)
    {
        var GrantType = "authorization_code";
        //client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
        var client = new HttpClient();
        client.BaseAddress = new Uri("https://api.surveymonkey.net");
        var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));

        var formData = new List<KeyValuePair<string, string>>();
        formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
        formData.Add(new KeyValuePair<string, string>("code", code));
        formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
        formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
        formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));

        request.Content = new FormUrlEncodedContent(formData);
        var response = await client.SendAsync(request);
        if (!response.IsSuccessStatusCode)
        {
            _errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
            return null;
        }

        var data = await response.Content.ReadAsStringAsync();
        if (data == null)
            return null;
        Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
        return(tokenInfo["access_token"]);
    }
}