2014-04-03 7 views
7

Ich versuche OAuth Bearer Authentication mit Owin zu implementieren. Wenn ein ungültiges oder abgelaufenes Token übergeben wird, protokolliert die Standardimplementierung dies als Warnung und legt keine Identität fest. Ich möchte jedoch die gesamte Anfrage mit einem Fehler in diesem Fall ablehnen. Aber wie würde ich das tun?Fehler bei ungültigem oder abgelaufenem Token zurückgegeben

Nach dem Durchstöbern des Codes fand ich heraus, dass in OAuthBearerAuthenticationHandler es das Token mit einem Fallback-Mechanismus analysieren wird, wenn die bereitgestellten AuthenticationTokenProvider kein Ticket analysiert (wie die Standardimplementierung). Dieser Handler protokolliert eine Warnung, wenn das Token nicht für ein Ticket analysiert werden konnte oder wenn es abgelaufen ist.

Aber ich kann keinen Platz finden, um meine eigene Logik an das anzuschließen, was passiert, wenn das Token ungültig oder abgelaufen ist. Ich könnte dies theoretisch selbst in der AuthenticationTokenProvider überprüfen, aber dann müsste ich die Logik neu implementieren (= es kopieren), um das Token zu erstellen und zu lesen. Auch dies scheint nur fehl am Platz zu sein, da diese Klasse nur für das Erstellen und Parsen von Token verantwortlich zu sein scheint. Ich sehe auch keine Möglichkeit, meine eigene Implementierung des OAuthBearerAuthenticationHandler in der OAuthBearerAuthenticationMiddleware anzuschließen.

Anscheinend wäre meine beste und sauberste Aufnahme, die ganze Middleware neu zu implementieren, aber das scheint auch sehr übertrieben zu sein.

Was vergesse ich? Wie würde ich das am besten weitermachen?

bearbeiten:

Zur Verdeutlichung. Ich weiß, indem ich keine Identität festlege, wird die Anfrage mit 401 Unauthorized später in der Web API abgelehnt. Aber ich persönlich sehe das als einen wirklich schlechten Stil an und verschlucke leise ein fehlerhaftes Zugriffstoken ohne jede Benachrichtigung. Auf diese Weise erfahren Sie nicht, dass Ihr Token Mist ist, Sie erfahren nur, dass Sie nicht autorisiert sind.

Antwort

0

Wenn die Authentifizierung fehlschlägt (was bedeutet, dass das Token abgelaufen ist), setzt diese Schicht den Benutzer nicht, wie Sie gesagt haben. Es ist die Autorisierungsschicht (später), um den Anruf abzulehnen. Für Ihr Szenario müsste Ihre Web-API den Zugriff auf einen anonymen Anrufer verweigern. Verwenden Sie das Autorisierungsfilterattribut [Autorisieren].

+0

Vielen Dank für Ihre Antwort, aber das ist keine Antwort auf meine Frage. Ich frage speziell, wie man ungültiges Zugriffstoken ablehnt. Ich verstehe, dass die Web-API selbst mit einem 401 Unauthorized ablehnt, halte das aber für einen sehr schlechten Stil. Wenn ein INVALID- oder ein ABGELAUFENes Zugriffstoken übergeben wird, sollte die Antwort eine Benachrichtigung sein, dass das Zugriffstoken UNGÜLTIG oder ABGELAUFEN ist - nicht, dass der Benutzer einfach nicht autorisiert ist. – user3137652

+0

Ich würde mich freuen, wenn Sie Ihre Antwort entfernen könnten, da sie meine Frage nicht beantwortet. Ich habe meine Frage bearbeitet und klar gemacht, dass mir das bewusst ist und dass ich das nicht will. – user3137652

+0

Sorry, Sie mögen die Antwort nicht, aber es ist das richtige Design. Die Authentifizierung ist getrennt von der Autorisierung. Dan Roth von Microsoft weist sogar in dieser aktuellen BUILD-Sitzung darauf hin: http://channel9.msdn.com/Events/Build/2014/3-603 –

6

hatte ich ein ähnliches Problem, ich denke, die Antwort auf spät ist, aber jemand wird hier mit einem ähnlichen Problem kommen:

ich dieses nuget Paket für Validate-Authentifizierung verwendet wird, aber ich denke, jede Methode helfen kann: https://www.nuget.org/packages/WebApi.AuthenticationFilter. Sie können seine Dokumentation auf dieser Website 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; 
}} 

Antwortbeispiele lesen:

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

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

Fonts und Inspiration Dokumentation:

//github.com/mbenford/WebApi-AuthenticationFilter

//www.asp.net/web-api/Überblick/security/authentication-Filter

2

Ja, ich habe nicht 'gut' Lösung dafür finden,

ich einen Weg nicht sehen, auch in meiner eigenen Implementierung zu stopfen der OAuthBearerAuthenticationHandler in der OAuthBearerAuthenticationMiddleware.

Anscheinend mein bester und sauberster Schuss würde sein, die ganze Middleware neu zu implementieren, aber das scheint auch sehr übertrieben.

Einverstanden, aber das ist was ich getan habe (bevor Sie Ihren Beitrag gelesen haben). Ich kopiere & klebte drei OWIN-Klassen, und machte es so, dass es die Eigenschaft im Owins-Kontext setzt, die später von anderen Handlern überprüft werden kann.

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); 
    } 
} 

Dann schrieb ich meine eigene Bevollmächtigung Filter, die weltweit angewendet werden:

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; 
    } 
} 

mein WebApiConfig:

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

Wie mein configureOAuth wie folgt aussieht:

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); 
} 

Ich werdeversuchenbekomme dies zum Hauptzweig von oAuth Middleware, es scheint wie ein offensichtlicher Anwendungsfall, es sei denn, ich vermisse etwas.

Verwandte Themen