web-dev-qa-db-ja.com

無効または期限切れのトークンでエラーを返す

OAuth Owinを使用したベアラー認証を実装しようとしています。無効または期限切れのトークンが渡された場合、デフォルトの実装では、これを警告としてログに記録し、IDを設定しません。Iただし、この場合はエラーでリクエスト全体を拒否したいのですが、どうすればよいですか?

コードを調べた後、OAuthBearerAuthenticationHandlerで、提供されたAuthenticationTokenProviderがチケットを解析しなかった場合(デフォルトの実装のように)、フォールバックメカニズムを使用してトークンを解析することがわかりました。このハンドラーは、トークンをどのチケットにも解析できなかった場合、またはチケットの有効期限が切れた場合に警告をログに記録します。

しかし、トークンが無効または期限切れになったときに何が起こるかについて、自分のロジックをプラグインする場所が見つかりません。理論的にはAuthenticationTokenProviderでこれを自分で確認できますが、トークンを作成して読み取るためのロジックを再実装(=コピーオーバー)する必要があります。また、このクラスはトークンの作成と解析のみを担当しているように見えるため、これは場違いのようです。また、OAuthBearerAuthenticationHandlerの独自の実装をOAuthBearerAuthenticationMiddlewareにプラグインする方法もわかりません。

どうやら私の最善かつ最もクリーンなショットはミドルウェア全体を再実装することですが、これも非常にやり過ぎのようです。

私は何を見落としていますか?私はこれについてどのように最善を尽くしますか?

編集:

明確にするために。 IDを設定しないことで、後でWebAPIの401Unauthorizedでリクエストが拒否されることを知っています。しかし、私は個人的にこれを本当に悪いスタイルだと考えており、通知なしに誤ったアクセストークンを黙って飲み込んでいます。このようにして、トークンががらくたであることを知ることができず、許可されていないことを知ることができます。

12
user3137652

私も同様の問題を抱えていました。答えは遅れると思いますが、誰かが同様の問題でここに来るでしょう:

認証の検証にこのnugetパッケージを使用しましたが、どの方法でも役立つと思います: https://www.nuget.org/packages/WebApi.AuthenticationFilter 。このサイトでドキュメントを読むことができます https://github.com/mbenford/WebApi-AuthenticationFilter

AuthenticationFilter.cs

public class AuthenticationFilter : AuthenticationFilterAttribute{
public override void OnAuthentication(HttpAuthenticationContext context)
{
    System.Net.Http.Formatting.MediaTypeFormatter jsonFormatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter();
    var ci = context.Principal.Identity as ClaimsIdentity;

    //First of all we are going to check that the request has the required Authorization header. If not set the Error
    var authHeader = context.Request.Headers.Authorization;
    //Change "Bearer" for the needed schema
    if (authHeader == null || authHeader.Scheme != "Bearer")
    {
        context.ErrorResult = context.ErrorResult = new AuthenticationFailureResult("unauthorized", context.Request,
            new { Error = new { Code = 401, Message = "Request require authorization" } });
    }
    //If the token has expired the property "IsAuthenticated" would be False, then set the error
    else if (!ci.IsAuthenticated)
    {
        context.ErrorResult = new AuthenticationFailureResult("unauthorized", context.Request,
            new { Error = new { Code = 401, Message = "The Token has expired" } });
    }
}}

AuthenticationFailureResult.cs

public class AuthenticationFailureResult : IHttpActionResult{
private object ResponseMessage;
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request, object responseMessage)
{
    ReasonPhrase = reasonPhrase;
    Request = request;
    ResponseMessage = responseMessage;
}

public string ReasonPhrase { get; private set; }

public HttpRequestMessage Request { get; private set; }

public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
    return Task.FromResult(Execute());
}

private HttpResponseMessage Execute()
{
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
    System.Net.Http.Formatting.MediaTypeFormatter jsonFormatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter();
    response.Content = new System.Net.Http.ObjectContent<object>(ResponseMessage, jsonFormatter);
    response.RequestMessage = Request;
    response.ReasonPhrase = ReasonPhrase;
    return response;
}}

応答例:

{"Error":{"Code":401,"Message":"Request require authorization"}}

{"Error":{"Code":401,"Message":"The Token has expired"}}

フォントとインスピレーションのドキュメント:

//github.com/mbenford/WebApi-AuthenticationFilter

//www.asp.net/web-api/overview/security/authentication-filters

7
jleon

ええ、私はこれに対する「良い」解決策を見つけられませんでした、

また、OAuthBearerAuthenticationMiddlewareにOAuthBearerAuthenticationHandlerの独自の実装をプラグインする方法もわかりません。

どうやら私の最善かつ最もクリーンなショットはミドルウェア全体を再実装することですが、これも非常にやり過ぎのようです。

同意しましたが、それは私がしたことです(あなたの投稿を読む前に)。 3つのowinクラスをコピーして貼り付け、Owinsコンテキストでプロパティを設定するように作成しました。これは、後で他のハンドラーで確認できます。

public static class OAuthBearerAuthenticationExtensions
{
    public static IAppBuilder UseOAuthBearerAuthenticationExtended(this IAppBuilder app, OAuthBearerAuthenticationOptions options)
    {
        if (app == null)
            throw new ArgumentNullException(nameof(app));

        app.Use(typeof(OAuthBearerAuthenticationMiddlewareExtended), app, options);
        app.UseStageMarker(PipelineStage.Authenticate);
        return app;
    }
}

internal class OAuthBearerAuthenticationHandlerExtended : AuthenticationHandler<OAuthBearerAuthenticationOptions>
{
    private readonly ILogger _logger;
    private readonly string _challenge;

    public OAuthBearerAuthenticationHandlerExtended(ILogger logger, string challenge)
    {
        _logger = logger;
        _challenge = challenge;
    }

    protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
    {
        try
        {
            // Find token in default location
            string requestToken = null;
            string authorization = Request.Headers.Get("Authorization");
            if (!string.IsNullOrEmpty(authorization))
            {
                if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                {
                    requestToken = authorization.Substring("Bearer ".Length).Trim();
                }
            }

            // Give application opportunity to find from a different location, adjust, or reject token
            var requestTokenContext = new OAuthRequestTokenContext(Context, requestToken);
            await Options.Provider.RequestToken(requestTokenContext);

            // If no token found, no further work possible
            if (string.IsNullOrEmpty(requestTokenContext.Token))
            {
                return null;
            }

            // Call provider to process the token into data
            var tokenReceiveContext = new AuthenticationTokenReceiveContext(
                Context,
                Options.AccessTokenFormat,
                requestTokenContext.Token);

            await Options.AccessTokenProvider.ReceiveAsync(tokenReceiveContext);
            if (tokenReceiveContext.Ticket == null)
            {
                tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
            }

            AuthenticationTicket ticket = tokenReceiveContext.Ticket;
            if (ticket == null)
            {
                _logger.WriteWarning("invalid bearer token received");
                Context.Set("oauth.token_invalid", true);
                return null;
            }

            // Validate expiration time if present
            DateTimeOffset currentUtc = Options.SystemClock.UtcNow;

            if (ticket.Properties.ExpiresUtc.HasValue &&
                ticket.Properties.ExpiresUtc.Value < currentUtc)
            {
                _logger.WriteWarning("expired bearer token received");
                Context.Set("oauth.token_expired", true);
                return null;
            }

            // Give application final opportunity to override results
            var context = new OAuthValidateIdentityContext(Context, Options, ticket);
            if (ticket != null &&
                ticket.Identity != null &&
                ticket.Identity.IsAuthenticated)
            {
                // bearer token with identity starts validated
                context.Validated();
            }
            if (Options.Provider != null)
            {
                await Options.Provider.ValidateIdentity(context);
            }
            if (!context.IsValidated)
            {
                return null;
            }

            // resulting identity values go back to caller
            return context.Ticket;
        }
        catch (Exception ex)
        {
            _logger.WriteError("Authentication failed", ex);
            return null;
        }
    }

    protected override Task ApplyResponseChallengeAsync()
    {
        if (Response.StatusCode != 401)
        {
            return Task.FromResult<object>(null);
        }

        AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);

        if (challenge != null)
        {
            OAuthChallengeContext challengeContext = new OAuthChallengeContext(Context, _challenge);
            Options.Provider.ApplyChallenge(challengeContext);
        }

        return Task.FromResult<object>(null);
    }
}


public class OAuthBearerAuthenticationMiddlewareExtended : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
{
    private readonly ILogger _logger;
    private readonly string _challenge;

    /// <summary>
    /// Bearer authentication component which is added to an OWIN pipeline. This constructor is not
    ///             called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
    ///             extension method.
    /// 
    /// </summary>
    public OAuthBearerAuthenticationMiddlewareExtended(OwinMiddleware next, IAppBuilder app, OAuthBearerAuthenticationOptions options)
      : base(next, options)
    {
        _logger = AppBuilderLoggerExtensions.CreateLogger<OAuthBearerAuthenticationMiddlewareExtended>(app);
        _challenge = string.IsNullOrWhiteSpace(Options.Challenge) ? (!string.IsNullOrWhiteSpace(Options.Realm) ? "Bearer realm=\"" + this.Options.Realm + "\"" : "Bearer") : this.Options.Challenge;

        if (Options.Provider == null)
            Options.Provider = new OAuthBearerAuthenticationProvider();

        if (Options.AccessTokenFormat == null)
            Options.AccessTokenFormat = new TicketDataFormat(
                Microsoft.Owin.Security.DataProtection.AppBuilderExtensions.CreateDataProtector(app, typeof(OAuthBearerAuthenticationMiddleware).Namespace, "Access_Token", "v1"));

        if (Options.AccessTokenProvider != null)
            return;

        Options.AccessTokenProvider = new AuthenticationTokenProvider();
    }

    /// <summary>
    /// Called by the AuthenticationMiddleware base class to create a per-request handler.
    /// 
    /// </summary>
    /// 
    /// <returns>
    /// A new instance of the request handler
    /// </returns>
    protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
    {
        return new OAuthBearerAuthenticationHandlerExtended(_logger, _challenge);
    }
}

次に、グローバルに適用される独自の承認フィルターを作成しました。

public class AuthorizeAttributeExtended : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        var tokenHasExpired = false;
        var owinContext = OwinHttpRequestMessageExtensions.GetOwinContext(actionContext.Request);
        if (owinContext != null)
        {
            tokenHasExpired = owinContext.Environment.ContainsKey("oauth.token_expired");
        }

        if (tokenHasExpired)
        {
            actionContext.Response = new AuthenticationFailureMessage("unauthorized", actionContext.Request,
                new
                {
                    error = "invalid_token",
                    error_message = "The Token has expired"
                });
        }
        else
        {
            actionContext.Response = new AuthenticationFailureMessage("unauthorized", actionContext.Request,
                new
                {
                    error = "invalid_request",
                    error_message = "The Token is invalid"
                });
        }
    }
}

public class AuthenticationFailureMessage : HttpResponseMessage
{
    public AuthenticationFailureMessage(string reasonPhrase, HttpRequestMessage request, object responseMessage)
        : base(HttpStatusCode.Unauthorized)
    {
        MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();

        Content = new ObjectContent<object>(responseMessage, jsonFormatter);
        RequestMessage = request;
        ReasonPhrase = reasonPhrase;
    }
}

私のWebApiConfig:

config.Filters.Add(new AuthorizeAttributeExtended());

ConfigureOAuthは次のようになります。

public void ConfigureOAuth(IAppBuilder app)
{
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    OAuthBearerOptions = new OAuthBearerAuthenticationOptions()
    {

    };

    OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10),

        Provider = new SimpleAuthorizationServerProvider(),
        RefreshTokenProvider = new SimpleRefreshTokenProvider(),
        AuthenticationMode =  AuthenticationMode.Active
    };

    FacebookAuthOptions = new CustomFacebookAuthenticationOptions();

    app.UseFacebookAuthentication(FacebookAuthOptions);
    app.UseOAuthAuthorizationServer(OAuthServerOptions);

    app.UseOAuthBearerAuthenticationExtended(OAuthBearerOptions);
}

これをoAuthミドルウェアのメインブランチに移動しようとします。何かが足りない場合を除いて、明らかなユースケースのようです。

2

認証が失敗した場合(トークンの有効期限が切れていることを意味します)、あなたが言ったように、そのレイヤーはユーザーを設定しません。呼び出しを拒否するのは、(後で)承認レイヤーの上位です。したがって、シナリオでは、WebAPIは匿名の呼び出し元へのアクセスを拒否する必要があります。 [承認]承認フィルター属性を使用します。

1
Brock Allen