2014-04-03 8 views
7

Próbuję zaimplementować uwierzytelnianie okaziciela OAuth przy użyciu karty Owin. Po przekazaniu nieważnego lub wygasłego tokena, domyślną implementacją jest zarejestrowanie tego jako ostrzeżenia i po prostu nie ustawianie Tożsamości. Chciałbym jednak odrzucić cały wniosek z błędem w tej sprawie. Ale jak miałbym to zrobić?Błąd powrotu na nieważnym lub wygasłym tokenie

Po wykopaniu kodu odkryłem, że w OAuthBearerAuthenticationHandler przeanalizuje token za pomocą mechanizmu rezerwowego, jeśli podany AuthenticationTokenProvider nie zanalizował żadnego biletu (jak domyślna implementacja). Ten program obsługi zarejestruje ostrzeżenie, gdy nie można przetworzyć tokena na żaden bilet lub gdy wygasł.

Ale nie mogę znaleźć miejsca do podłączenia mojej logiki do tego, co stanie się, gdy token jest nieważny lub wygasł. Teoretycznie mogłem to sprawdzić na własną rękę w AuthenticationTokenProvider, ale wtedy musiałbym ponownie zaimplementować logikę (= skopiuj) do tworzenia i odczytywania tokena. Również wydaje się to po prostu nie na miejscu, ponieważ ta klasa wydaje się być odpowiedzialna tylko za tworzenie i analizowanie tokenów. Również nie widzę sposobu, aby podłączyć moją własną implementację OAuthBearerAuthenticationHandler w OAuthBearerAuthenticationMiddleware.

Najwyraźniej moim najlepszym i najczystszym ujęciem byłoby ponowne wdrożenie całego oprogramowania pośredniego, ale wydaje się to również przesadą.

Co przeoczyć? Jak mógłbym o tym powiedzieć najlepiej?

edycja:

W celu wyjaśnienia. Wiem, że nie ustalając tożsamości, żądanie zostanie odrzucone z 401 Nieautoryzowanym później w Web API. Ale osobiście postrzegam to jako naprawdę zły styl, po cichu połknąwszy błędny token dostępu bez żadnego powiadomienia. W ten sposób nie dowiesz się, że token jest bzdura, po prostu wiesz, że nie masz autoryzacji.

Odpowiedz

0

Jeśli uwierzytelnienie nie powiedzie się (co oznacza, że ​​token wygasł), warstwa ta nie ustawi użytkownika, jak powiedziałeś. Przejście warstwy autoryzacji (później) powoduje odrzucenie połączenia. W związku z tym Twój scenariusz Web API musiałby odmówić dostępu anonimowemu rozmówcy. Użyj atrybutu filtra autoryzacji [Autoryzuj].

+0

Dziękuję za odpowiedź, ale to nie jest odpowiedź na moje pytanie. W szczególności pytam o to, jak odrzucić tokena nieważnego dostępu. Rozumiem, że Web API sam odrzuca 401 nieautoryzowane, ale uważam ten bardzo zły styl. Jeśli zostanie przekazany token dostępu NIEPRAWIDŁOWY lub EXPIRED, odpowiedzią powinno być powiadomienie, że token dostępu jest NIEPRAWIDŁOWY lub WYDANY - nie oznacza to, że użytkownik nie jest po prostu uprawniony. – user3137652

+0

Byłbym zadowolony, gdyby można było usunąć odpowiedź, ponieważ nie odpowiada na moje pytanie. Zmieniłem moje pytanie i wyjaśniłem, że jestem tego świadomy i że tego nie chcę. – user3137652

+0

Niestety nie podoba ci się odpowiedź, ale jest to właściwy projekt. Uwierzytelnianie jest niezależne od autoryzacji. Dan Roth z Microsoftu zwraca na to uwagę podczas ostatniej sesji BUILD: http://channel9.msdn.com/Events/Build/2014/3-603 –

6

Miałem podobny problem, myślę, że odpowiedź jest późno, ale ktoś przyjdzie tu z podobnym problemem:

użyłem tego Nuget pakiet dla uwierzytelniania walidacji, ale myślę, że każda metoda może pomóc: https://www.nuget.org/packages/WebApi.AuthenticationFilter. można przeczytać jego dokumentacji w tym miejscu 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; 
}} 

przykłady reakcji:

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

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

Czcionki i dokumentacja inspiracja:

//github.com/mbenford/WebApi-AuthenticationFilter

//www.asp.net/web-api/Przegląd/Bezpieczeństwo/authentication-filtry

2

Tak, nie mogę znaleźć „dobre” rozwiązanie tego problemu,

ja również nie widzę sposobu na wtyczki w moim realizacji usługi OAuthBearerAuthenticationHandler w OAuthBearerAuthenticationMiddleware.

Najwyraźniej moim najlepszym i najczystszym ujęciem byłoby ponowne wdrożenie całego oprogramowania pośredniego , ale wydaje się to również przesadą.

Zgadzam się, ale to właśnie zrobiłem (przed przeczytaniem postu). Skopiowałem & wklejono trzy klasy owin i ustawiłem je tak, aby ustawiały właściwość w kontekście Owins, które mogą później sprawdzić inne programy obsługi.

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

Potem napisałem swój własny filtr zezwoleń, która zostanie zastosowana globalnie:

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

mój WebApiConfig:

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

Jak mój configureOAuth wygląda następująco:

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

Spróbuję & dostać się do głównej gałęzi oprogramowania pośredniczącego oAuth, wydaje się oczywistym przypadkiem użycia, chyba że czegoś brakuje.

Powiązane problemy