身份验证和授权 - HTTP 请求中的令牌 body
Authentication & Authorization - Token in HTTP request body
我正在尝试创建一个自定义身份验证处理程序,它需要 HTTP 请求的 body 中的 Bearer JWT
,但我不希望创建一个全新的自定义授权。不幸的是,我唯一能做的就是读取 HTTP 请求 body,从那里获取令牌并将其放入请求的授权 header 中。
是否有其他更有效的方法?我所做的只是在 GitHub 上找到默认的 JwtBearerHandler
实现,但是当我进行一些修改时,它无法正确读取主体。
Startup.cs:
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
ctx.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
public class AuthHandler : JwtBearerHandler
{
private readonly IRepositoryEvonaUser _repositoryUser;
private OpenIdConnectConfiguration _configuration;
public AuthHandler(IOptionsMonitor<JwtBearerOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
IDataProtectionProvider dataProtection,
ISystemClock clock,
IRepositoryUser repositoryUser,
OpenIdConnectConfiguration configuration
)
: base(options, logger, encoder, dataProtection, clock)
{
_repositoryUser = repositoryUser;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
Request.EnableBuffering();
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, true, 10, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
if (body != null)
{
token = body.Token;
}
Request.Body.Position = 0;
}
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
}
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal; // it can't find this
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
}
或者,有没有办法告诉应用程序在 HTTP 请求中期待 JWT body?我很清楚应该在请求 header 而不是 body 中发送令牌,但我有兴趣看看是否(如果是,如何)这可以实现。
我也试过这个:
OnMessageReceived = ctx =>
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 10, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
if (body != null)
{
ctx.Token = body.Token;
ctx.Request.Body.Position = 0;
}
}
return Task.CompletedTask;
}
默认情况下,AddJwtBearer
将从请求 header 获取令牌,您应该编写逻辑以从请求 body 读取令牌并验证令牌。这意味着 "tell" 中间件没有这样的配置来读取令牌表单请求 body 。
如果在请求body中发送了token,需要在jwt中间件到达之前,在中间件中读取请求body,并在header中放入token。或者在jwt承载中间件的事件之一中读取请求body,例如,OnMessageReceived
event , read token in request body and at last set token like : context.Token = token;
. Here是读取中间件中的请求body的代码示例。
我会将@Nan Yu 的回答标记为正确答案,但我还是会 post 我的最终代码。我实际上所做的是恢复到默认 JwtBearerHandler
并使用 JwtBearerOptions
和 JwtBearerEvents
的 OnMessageReceived
事件从 HTTP 请求的主体中获取令牌值。
它们都位于 Microsoft.AspNetCore.Authentication.JwtBearer
命名空间中。
services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
ctx.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
},
OnMessageReceived = ctx =>
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 1024, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
ctx.Request.Body.Position = 0;
if (body != null)
{
ctx.Token = body.Token;
}
}
return Task.CompletedTask;
}
};
});
我正在尝试创建一个自定义身份验证处理程序,它需要 HTTP 请求的 body 中的 Bearer JWT
,但我不希望创建一个全新的自定义授权。不幸的是,我唯一能做的就是读取 HTTP 请求 body,从那里获取令牌并将其放入请求的授权 header 中。
是否有其他更有效的方法?我所做的只是在 GitHub 上找到默认的 JwtBearerHandler
实现,但是当我进行一些修改时,它无法正确读取主体。
Startup.cs:
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
ctx.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
public class AuthHandler : JwtBearerHandler
{
private readonly IRepositoryEvonaUser _repositoryUser;
private OpenIdConnectConfiguration _configuration;
public AuthHandler(IOptionsMonitor<JwtBearerOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
IDataProtectionProvider dataProtection,
ISystemClock clock,
IRepositoryUser repositoryUser,
OpenIdConnectConfiguration configuration
)
: base(options, logger, encoder, dataProtection, clock)
{
_repositoryUser = repositoryUser;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
Request.EnableBuffering();
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, true, 10, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
if (body != null)
{
token = body.Token;
}
Request.Body.Position = 0;
}
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
}
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal; // it can't find this
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
}
或者,有没有办法告诉应用程序在 HTTP 请求中期待 JWT body?我很清楚应该在请求 header 而不是 body 中发送令牌,但我有兴趣看看是否(如果是,如何)这可以实现。
我也试过这个:
OnMessageReceived = ctx =>
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 10, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
if (body != null)
{
ctx.Token = body.Token;
ctx.Request.Body.Position = 0;
}
}
return Task.CompletedTask;
}
默认情况下,AddJwtBearer
将从请求 header 获取令牌,您应该编写逻辑以从请求 body 读取令牌并验证令牌。这意味着 "tell" 中间件没有这样的配置来读取令牌表单请求 body 。
如果在请求body中发送了token,需要在jwt中间件到达之前,在中间件中读取请求body,并在header中放入token。或者在jwt承载中间件的事件之一中读取请求body,例如,OnMessageReceived
event , read token in request body and at last set token like : context.Token = token;
. Here是读取中间件中的请求body的代码示例。
我会将@Nan Yu 的回答标记为正确答案,但我还是会 post 我的最终代码。我实际上所做的是恢复到默认 JwtBearerHandler
并使用 JwtBearerOptions
和 JwtBearerEvents
的 OnMessageReceived
事件从 HTTP 请求的主体中获取令牌值。
它们都位于 Microsoft.AspNetCore.Authentication.JwtBearer
命名空间中。
services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
ctx.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
},
OnMessageReceived = ctx =>
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 1024, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
ctx.Request.Body.Position = 0;
if (body != null)
{
ctx.Token = body.Token;
}
}
return Task.CompletedTask;
}
};
});