简体   繁体   English

无效或过期令牌返回错误

[英]Return error on invalid or expired token

I'm trying to implement OAuth Bearer Authentication with Owin.我正在尝试使用 Owin 实现 OAuth 承载身份验证。 When an invalid or expired token is passed, the default implementation is to log this as a warning and just don't set an Identity.当传递无效或过期的令牌时,默认实现是将其记录为警告并且不设置身份。 I however would like to reject the whole request with an error in this case.但是,在这种情况下,我想拒绝整个请求并出现错误。 But how would I do this?但是我该怎么做呢?

After digging through the code I found out that in OAuthBearerAuthenticationHandler it will parse the token using a fallback mechanism when the provided AuthenticationTokenProvider did not parse any ticket (like the default implementation).在深入研究代码后,我发现在OAuthBearerAuthenticationHandler ,当提供的AuthenticationTokenProvider没有解析任何票证(如默认实现)时,它将使用回退机制解析令牌。 This handler will log a warning when the token could not be parsed to any ticket or when it expired.当令牌无法解析为任何票证或过期时,此处理程序将记录警告。

But I can't find any place to plug in my own logic to what happens when the token is invalid or expired.但是我找不到任何地方可以将我自己的逻辑插入令牌无效或过期时会发生的情况。 I could theoretically check this on my own in the AuthenticationTokenProvider , but then I would have to reimplement the logic (= copy it over) for creating and reading the token.理论上我可以在AuthenticationTokenProvider自己检查这个,但是我必须重新实现逻辑(=复制它)来创建和读取令牌。 Also this seems just out of place, as this class seems to be only responsible for creating and parsing tokens.这似乎也不合适,因为这个类似乎只负责创建和解析令牌。 I also don't see a way to plug in my own implementation of the OAuthBearerAuthenticationHandler in the OAuthBearerAuthenticationMiddleware .我也没有看到在OAuthBearerAuthenticationHandler中插入我自己的OAuthBearerAuthenticationHandler实现的OAuthBearerAuthenticationMiddleware

Apparently my best and cleanest shot would be to reimplement the whole middleware, but this also seems very overkill.显然,我最好和最干净的方法是重新实现整个中间件,但这似乎也太过分了。

What do I overlook?我忽略了什么? How would I go on about this the best?我将如何继续这个最好的?

edit:编辑:

For clarification.为了澄清。 I know by not setting an identity the request will be rejected with 401 Unauthorized later in the Web API.我知道如果不设置身份,请求将在稍后的 Web API 中被 401 Unauthorized 拒绝。 But I personally see this as really bad style, silently swallowing an erroneous access token without any notification.但我个人认为这是非常糟糕的风格,在没有任何通知的情况下默默吞下错误的访问令牌。 This way you don't get to know that your token is crap, you just get to know you're not authorized.这样你就不会知道你的令牌是垃圾,你只会知道你没有被授权。

I had a similar issue, i think the answer is to late but someone will come here with a similar problem:我有一个类似的问题,我认为答案来晚了,但有人会带着类似的问题来到这里:

I used this nuget package for validate authentication, but i think any method can help: https://www.nuget.org/packages/WebApi.AuthenticationFilter .我使用这个 nuget 包进行验证身份验证,但我认为任何方法都可以提供帮助: https ://www.nuget.org/packages/WebApi.AuthenticationFilter 。 You can read its documentation in this site https://github.com/mbenford/WebApi-AuthenticationFilter您可以在此站点https://github.com/mbenford/WebApi-AuthenticationFilter 中阅读其文档

AuthenticationFilter.cs 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认证失败结果.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;
}}

Response examples:响应示例:

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

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

Fonts and inspiration documentation:字体和灵感文档:

//github.com/mbenford/WebApi-AuthenticationFilter //github.com/mbenford/WebApi-AuthenticationFilter

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

Yeah, I did not find 'good' solution for this,是的,我没有找到“好的”解决方案,

I also don't see a way to plug in my own implementation of the OAuthBearerAuthenticationHandler in the OAuthBearerAuthenticationMiddleware.我也没有看到在 OAuthBearerAuthenticationMiddleware 中插入我自己的 OAuthBearerAuthenticationHandler 实现的方法。

Apparently my best and cleanest shot would be to reimplement the whole middleware, but this also seems very overkill.显然,我最好和最干净的方法是重新实现整个中间件,但这似乎也太过分了。

Agreed, but that's what I did (before reading your post).同意,但这就是我所做的(在阅读您的帖子之前)。 I copy & pasted three owin classes, and made it so that it sets property in Owins context, which can be later checked by other handlers.我复制并粘贴了三个 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);
    }
}

Then I wrote my own authorization filter, which will be applied globally:然后我写了我自己的授权过滤器,它将被全局应用:

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

my WebApiConfig:我的 WebApiConfig:

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

How my configureOAuth looks like:我的 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);
}

I will try & get this to main branch of oAuth middleware, it seems like an obvious use case, unless I am missing something.我将尝试将其发送到 oAuth 中间件的主分支,这似乎是一个明显的用例,除非我遗漏了什么。

I came across this problem recently.我最近遇到了这个问题。 We wanted to return a JSON message if the user's access token had expired, allowing the consumer web application to silently refresh the access token and re-issue the API request.如果用户的访问令牌已过期,我们希望返回一条 JSON 消息,允许消费者 Web 应用程序静默刷新访问令牌并重新发出 API 请求。 We also didn't want to rely on the exceptions thrown for token lifetime validation.我们也不想依赖为令牌生命周期验证抛出的异常。

Not wanting to re-implement any middleware, we specified the Provider option inside JwtBearerAuthenticationOptions and added a delegate to handle the OnRequestTokenMethod.不想重新实现任何中间件,我们在 JwtBearerAuthenticationOptions 中指定了 Provider 选项并添加了一个委托来处理 OnRequestTokenMethod。 The delegate checks to see if it can read the token passed to the middleware and sets a boolean inside the OWIN context if it's expired.委托检查它是否可以读取传递给中间件的令牌,并在 OWIN 上下文中设置一个布尔值(如果它已过期)。

app.UseJwtBearerAuthentication(
             new JwtBearerAuthenticationOptions
             {
                 AuthenticationMode = AuthenticationMode.Active,
                 TokenValidationParameters = tokenValidationParameters,                                             
                 Provider = new OAuthBearerAuthenticationProvider
                 {                         
                     OnRequestToken = (ctx) =>
                     {                             
                         if (!string.IsNullOrEmpty(ctx.Token))
                         {
                             JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                             if (handler.CanReadToken(ctx.Token))
                             {
                                 JwtSecurityToken jwtToken = handler.ReadJwtToken(ctx.Token);
                                 
                                 if (jwtToken.IsExpired())
                                     ctx.OwinContext.Set<bool>("expiredToken", true);
                             }                                                                  
                         }

                         return Task.CompletedTask;
                     }
                 }
             });            

For convenience I added a quick extension method to check if a JWT expired:为方便起见,我添加了一个快速扩展方法来检查 JWT 是否过期:

    public static class JwtSecurityTokenExtensions
    {        
        public static bool IsExpired (this JwtSecurityToken token)
        {
            if (DateTime.UtcNow > token.ValidTo.ToUniversalTime())
                return true;

            return false;
        }
    }

We ended up using a middleware to check on the state of that boolean:我们最终使用了一个中间件来检查该布尔值的状态:

app.Use((context, next) =>
        {
            bool expiredToken = context.Get<bool>("expiredToken");
            
            if (expiredToken)
            {
                // do stuff
            }

            return next.Invoke();
        });
        app.UseStageMarker(PipelineStage.Authenticate);

Not exactly the most efficient code, since we're parsing the token again after the middleware already did and also introducing a new middleware to act on the result of the check, but it's a fresh perspective nonetheless.不完全是最有效的代码,因为我们在中间件已经完成之后再次解析令牌,并且还引入了一个新的中间件来对检查的结果采取行动,但它仍然是一个全新的视角。

If authentication fails (meaning the token is expired) then that layer doesn't set the user, as you said.如果身份验证失败(意味着令牌已过期),则该层不会设置用户,如您所说。 It's up the the authorization layer (later on) to reject the call.拒绝呼叫由授权层(稍后)决定。 So for your scenario your Web API would need to deny access to an anonymous caller.因此,对于您的场景,您的 Web API 需要拒绝匿名调用者的访问。 Use the [Authorize] authorization filter attribute.使用 [Authorize] 授权过滤器属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM