返回无效或过期令牌的错误
本文关键字:错误 令牌 过期 无效 返回 | 更新日期: 2023-09-27 17:56:21
我正在尝试使用 Owin 实现 OAuth 持有者身份验证。传递无效或过期的令牌时,默认实现是将其记录为警告,并且不设置标识。但是,在这种情况下,我想拒绝整个请求并出现错误。但是我该怎么做呢?
在挖掘代码后,我发现在OAuthBearerAuthenticationHandler
中,当提供的AuthenticationTokenProvider
没有解析任何票证(如默认实现)时,它将使用回退机制解析令牌。当令牌无法解析为任何票证或过期时,此处理程序将记录警告。
但是我找不到任何地方来插入我自己的逻辑,当令牌无效或过期时会发生什么。理论上我可以在AuthenticationTokenProvider
中自己检查这一点,但是然后我必须重新实现逻辑(=复制它)来创建和读取令牌。此外,这似乎不合适,因为此类似乎只负责创建和解析令牌。我也看不到在OAuthBearerAuthenticationMiddleware
中插入我自己的OAuthBearerAuthenticationHandler
实现的方法。
显然,我最好和最干净的选择是重新实现整个中间件,但这似乎也非常矫枉过正。
我忽略了什么?我该如何最好地继续下去?
编辑:
为了澄清。我知道如果不设置标识,请求稍后将在 Web API 中被拒绝,并显示 401 未授权。但我个人认为这是非常糟糕的风格,在没有任何通知的情况下默默吞下错误的访问令牌。这样你就不会知道你的令牌是垃圾,你只会知道你没有被授权。
我遇到了类似的问题,我认为答案是迟到,但有人会带着类似的问题来到这里:
我使用这个 nuget 包来验证身份验证,但我认为任何方法都可以提供帮助:https://www.nuget.org/packages/WebApi.AuthenticationFilter。您可以在本网站阅读其文档 https://github.com/mbenford/WebApi-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" } });
}
}}
身份验证失败结果.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
是的,我没有为此找到"好"的解决方案,
我也看不到插入我自己的实现的方法 OAuthBearerAuthenticationHandler in OAuthBearerAuthenticationMiddleware.
显然,我最好和最干净的镜头是重新实现整个 中间件,但这似乎也非常矫枉过正。
同意,但这就是我所做的(在阅读您的帖子之前)。我复制并粘贴了三个 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;
}
}
我的网络配置:
config.Filters.Add(new AuthorizeAttributeExtended());
我的配置OAuth是什么样的:
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中间件的主分支,这似乎是一个明显的用例,除非我缺少某些内容。
我最近遇到了这个问题。我们希望在用户的访问令牌已过期时返回 JSON 消息,从而允许使用者 Web 应用程序以静默方式刷新访问令牌并重新发出 API 请求。我们也不想依赖引发的异常进行令牌生存期验证。
不想重新实现任何中间件,我们在 JwtBearerAuthenticationOptions 中指定了 Provider 选项,并添加了一个委托来处理 OnRequestTokenMethod。委托检查它是否可以读取传递给中间件的令牌,并在 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;
}
}
});
为方便起见,我添加了一个快速扩展方法来检查 JWT 是否过期:
public static class JwtSecurityTokenExtensions
{
public static bool IsExpired (this JwtSecurityToken token)
{
if (DateTime.UtcNow > token.ValidTo.ToUniversalTime())
return true;
return false;
}
}
我们最终使用中间件来检查该布尔值的状态:
app.Use((context, next) =>
{
bool expiredToken = context.Get<bool>("expiredToken");
if (expiredToken)
{
// do stuff
}
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
这不是最有效的代码,因为我们在中间件已经解析后再次解析令牌,并且还引入了一个新的中间件来对检查结果进行操作,但这仍然是一个全新的视角。
如果身份验证失败(意味着令牌已过期),则该层不会设置用户,如您所说。授权层(稍后)拒绝呼叫。因此,对于你的方案,Web API 需要拒绝匿名调用方的访问。使用 [授权] 授权过滤器属性。