2014-04-03 22 views
7

Sto tentando di implementare l'autenticazione OAuth Bearer con Owin. Quando viene passato un token non valido o scaduto, l'implementazione predefinita deve registrare questo come un avvertimento e semplicemente non impostare un'Identità. Tuttavia, vorrei respingere l'intera richiesta con un errore in questo caso. Ma come lo farei?Errore di restituzione per token non valido o scaduto

Dopo aver scavato attraverso il codice ho scoperto che in OAuthBearerAuthenticationHandler si analizzerà il token mediante un meccanismo di ripiego quando la condizione AuthenticationTokenProvider non ha analizzare ciascun biglietto (come l'implementazione di default). Questo gestore registrerà un avviso quando il token non può essere analizzato su alcun ticket o quando è scaduto.

Ma non riesco a trovare alcun posto per collegare la mia logica a cosa succede quando il token non è valido o è scaduto. Potrei teoricamente controllarlo da solo nel AuthenticationTokenProvider, ma poi dovrei reimplementare la logica (= copiarla) per creare e leggere il token. Anche questo sembra fuori posto, in quanto questa classe sembra essere solo responsabile della creazione e dell'analisi di token. Inoltre, non vedo il modo di collegare la mia implementazione dello OAuthBearerAuthenticationHandler allo OAuthBearerAuthenticationMiddleware.

Apparentemente il mio scatto migliore e più pulito sarebbe quello di reimplementare l'intero middleware, ma anche questo sembra molto eccessivo.

Che cosa trascuro? Come potrei continuare su questo il meglio?

modifica:

Per chiarimenti. So che non impostando un'identità la richiesta verrà rifiutata con 401 Non autorizzato più tardi nell'API Web. Ma personalmente considero questo stile davvero pessimo, inghiottendo silenziosamente un token di accesso errato senza alcuna notifica. In questo modo non ti rendi conto che il tuo token fa schifo, devi solo sapere che non sei autorizzato.

risposta

0

Se l'autenticazione non riesce (ovvero il token è scaduto), quel livello non imposta l'utente, come hai detto. È in arrivo il livello di autorizzazione (in seguito) per rifiutare la chiamata. Quindi per il tuo scenario la tua API Web dovrebbe negare l'accesso a un chiamante anonimo. Utilizzare l'attributo del filtro di autorizzazione [Autorizza].

+0

Grazie per la risposta, ma questa non è una risposta alla mia domanda. Mi sto chiedendo in particolare su come rifiutare il token di accesso non valido. Capisco che la stessa API Web si rifiuti con una versione non autorizzata del 401, ma considero questo stile pessimo. Se viene passato un token di accesso INVALID o EXPIRED, la risposta dovrebbe essere una notifica che il token di accesso è NON VALIDO o SCADUTO - non che l'utente semplicemente non è autorizzato. – user3137652

+0

Sarei felice se potessi rimuovere la tua risposta, dal momento che non risponde alla mia domanda. Ho modificato la mia domanda e ho chiarito che sono consapevole di ciò, e che questo non è ciò che voglio. – user3137652

+0

Scusa se non ti piace la risposta, ma è il design corretto. L'autenticazione è separata dall'autorizzazione. Dan Roth di Microsoft lo fa notare anche in questa recente sessione BUILD: http://channel9.msdn.com/Events/Build/2014/3-603 –

6

ho avuto un problema simile, credo che la risposta è troppo tardi, ma qualcuno verrà qui con un problema simile:

Ho usato questo pacchetto NuGet per l'autenticazione validate, ma penso che qualsiasi metodo può aiutare: https://www.nuget.org/packages/WebApi.AuthenticationFilter. È possibile leggere la documentazione in questo sito 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; 
}} 

esempi di risposta:

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

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

font e documentazione ispirazione:

//github.com/mbenford/WebApi-AuthenticationFilter

//www.asp.net/web-api/panoramica/sicurezza/autenticazione-filtri

2

Sì, non ho trovato una soluzione 'buona' per questo,

anche io non vedo un modo per collegare il mio propria implementazione di OAuthBearerAuthenticationHandler nel OAuthBearerAuthenticationMiddleware.

Apparentemente il mio scatto migliore e più pulito sarebbe quello di reimplementare l'intero middleware , ma anche questo sembra molto eccessivo.

D'accordo, ma è quello che ho fatto (prima di leggere il tuo post). Copio & incollato tre classi owin e lo faccio in modo che imposti la proprietà nel contesto di Owins, che può essere successivamente controllato da altri gestori.

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

Poi ho scritto il mio filtro di autorizzazione, che sarà applicata a livello globale:

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

mia WebApiConfig:

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

Come mio configureOAuth assomiglia:

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

Proverò & ottenere questo al ramo principale del middleware outh, sembra un caso d'uso ovvio, a meno che mi manca qualcosa.

Problemi correlati