web-dev-qa-db-ja.com

JSONを使用したWebAPI2.0OWINトークンリクエスト

Visual Studioで新しいWebAPIソリューションを作成し、コードをいじって何が起こっているのかを理解しようとしています。

承認コントローラーと実際のすべての機能を実装する別のコントローラーですべて稼働しているテストAPIがあります。

/ Tokenリクエストを除いて、コントローラー(API)はすべてJSONを受信し、JSONで応答することで機能します。これは次のようにする必要があります。

Content-Type: application/x-www-form-urlencoded

そうしないと、エラーが返されます。

このエンドポイントを作成するコードのセクションは次のようになります。

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // In production mode set AllowInsecureHttp = false
    AllowInsecureHttp = false
};

このように呼び出すと、ベアラートークンを使用して200の成功応答が返されます。

$("#token_button").click(function ()
{
    var username = $("#token_email").val();
    var password = $("#token_password").val();

    postData("Token", "grant_type=password&username=" + username + "&password=" + password, "application/x-www-form-urlencoded", function (data)
    {
        user = data;
        $("#feedback_display").html(user.access_token);
    }, function ()
    {
        user = null;
    });
});

このように呼び出すと、400応答になります。

$("#token_button").click(function ()
{
    var username = $("#token_email").val();
    var password = $("#token_password").val();

    var data = {
        "grant_type": "password",
        "username": username,
        "password": password
    }

    postData("Token", JSON.stringify(data), "application/json", function (data)
    {
        user = data;
        $("#feedback_display").html(user.access_token);
    }, function ()
    {
        user = null;
    });
});

応答本文は次のとおりです。

{"error":"unsupported_grant_type"}

ここでの唯一の違いは、リクエストの送信に使用されるエンコーディングです。すべての例を見ると、フォームエンコーディングを使用してこのトークンを要求しています。

/ api/Account/ExternalLoginの下のコードにブレークポイントを設定しても、ヒットすることはありません。

これがフォームエンコーディングのみを受け入れる理由はありますか?そうでない場合は、JSONを受け入れるようにコントローラーを変更するにはどうすればよいですか?

あるいは、私は愚かなことをしたことがありますか?

15
Morvael

application/x-www-form-urlencodedContent-Typeとして使用する理由は単純です。 OAuth2仕様(RFC 6749) は、トークン要求にこのコンテンツタイプを必要とします。

その他のコンテンツタイプは、OAuth2準拠のクライアントの互換性を損ないます。この標準的な動作を変更しないことをお勧めします。


これに注意してください:

postData("Token", data, "application/json", function (data)
{
    //...
}

jSONを送信していないという理由だけで機能します! application/jsonContent-Typeヘッダーとして追加した場合でも、リクエストの本文はフォームのキーと値のペアとしてシリアル化されます(AJAX呼び出しでのjQueryのデフォルトオブジェクトのシリアル化)。

Microsoft.Owin.Security.OAuthからのOAuthAuthorizationServerMiddleware(より正確には内部で使用される OAuthAuthorizationServerHandler )のデフォルトの実装は、Content-Typeヘッダーを無視し、とにかくフォームとして本文をリクエストしてください。

14
Federico Dipuma

データを直接JSON.stringify(data)渡す必要はありません。

1
gaurav bhavsar

顧客の技術スペシャリストが、/ tokenエンドポイントで「application/x-www-form-urlencoded」形式と「application/json」形式の両方を本文で使用できるように要求しました。そのため、仕様に反しているにもかかわらず、実装する必要がありました。

パスが「/ api/token」で、content-typeが「application/json」の場合、JSON本体をUrlエンコードされた本体に変換するOwinミドルウェアを作成します。 Startup.csに登録することを忘れないでください。

public sealed class JsonBodyToUrlEncodedBodyMiddleware : OwinMiddleware
    {
        public JsonBodyToUrlEncodedBodyMiddleware(OwinMiddleware next)
            : base(next)
        {
        }

        public override async Task Invoke(IOwinContext context)
        {
            if (string.Equals(context.Request.ContentType, "application/json")
                && string.Equals(context.Request.Method, "POST", StringComparison.InvariantCultureIgnoreCase)
                && context.Request.Path == new PathString("/avi/token/"))
            {
                try
                {
                    await ReplaceJsonBodyWithUrlEncodedBody(context);
                    await Next.Invoke(context);
                }
                catch (Exception)
                {
                    context.Response.StatusCode = (int) HttpStatusCode.BadRequest;
                    context.Response.Write("Invalid JSON format.");
                }
            }
            else
            {
                await Next.Invoke(context);
            }
        }

        private async Task ReplaceJsonBodyWithUrlEncodedBody(IOwinContext context)
        {
            var requestParams = await GetFormCollectionFromJsonBody(context);
            var urlEncodedParams = string.Join("&", requestParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
            var decryptedContent = new StringContent(urlEncodedParams, Encoding.UTF8, "application/x-www-form-urlencoded");
            var requestStream = await decryptedContent.ReadAsStreamAsync();
            context.Request.Body = requestStream;
        }

        private static async Task<Dictionary<string, string>> GetFormCollectionFromJsonBody(IOwinContext context)
        {
            context.Request.Body.Position = 0;
            var jsonString = await new StreamReader(context.Request.Body).ReadToEndAsync();
            var requestParams = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonString);
            return requestParams;
        }
    }
1
tseshevsky

OAuth2では、トークンリクエストにapplication/x-www-form-urlencodedコンテンツタイプが必要です。

それでも、私はこの回避策について考えました:

    // GET api/Account/GetToken
    [HttpPost]
    [AllowAnonymous]
    [Route("GetToken")]
    public async Task<IHttpActionResult> GetToken(TokenRequest request)
    {
        var client = new HttpClient()
        {
            BaseAddress = new Uri(Request.RequestUri.GetLeftPart(UriPartial.Authority))
        };

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "password"),
            new KeyValuePair<string, string>("username", request.Username),
            new KeyValuePair<string, string>("password", request.Password)
        });

        var result = await client.PostAsync("/token", content);
        string resultContent = await result.Content.ReadAsStringAsync();
        resultContent = resultContent.Replace(".issued", "issued").Replace(".expires", "expires");
        TokenResponse tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(resultContent);

        return Ok(tokenResponse);
    }

モデル:

    public class TokenRequest
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }

    public class TokenResponse
    {
        public string access_token { get; set; }
        public string token_type { get; set; }
        public int expires_in { get; set; }
        public string userName { get; set; }
        public DateTime issued { get; set; }
        public DateTime expires { get; set; }
        public string error { get; set; }
        public string error_description { get; set; }
    }

改善することはできますが、うまく機能します。

1
ethvlad