在Blazor WebAssembly中使用JWT时,我收到了来自Api的意外的未经授权的响应。注意,我并不试图保护WebAssembly客户机上的任何东西;只是保护API端点。我故意忽略了有效期的验证。
服务器
appsettings.json
{
"JwtSecurity": {
"Key": "RANDOM_KEY_MUST_NOT_BE_SHARED",
"Issuer": "https://localhost",
"Audience": "https://localhost",
"ExpiryDays": 1
}
}Program.cs
// Service registration
builder.Services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JwtSecurity:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["JwtSecurity:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSecurity:Key"])),
RequireExpirationTime = false,
ValidateLifetime = false
};
});
// Configure the HTTP request pipeline.
// SignalR Compression
app.UseResponseCompression();
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
// Logs the received token
app.UseJwtTokenHandler();
//explicitly only use blazor when the path doesn't start with api
app.MapWhen(ctx => !ctx.Request.Path.StartsWithSegments("/api"), blazor =>
{
blazor.UseBlazorFrameworkFiles();
blazor.UseStaticFiles();
blazor.UseRouting();
blazor.UseEndpoints(endpoints =>
{
endpoints.MapHub<Cosmos.App.Server.Hubs.TillSiteHub>("/tradingsessionhub");
endpoints.MapFallbackToFile("index.html");
});
});
//explicitly map api endpoints only when path starts with api
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), api =>
{
api.UseStaticFiles();
api.UseRequestLogging();
api.UseRouting();
api.UseAuthentication();
api.UseAuthorization();
// HAVE ALSO TRIED
// api.UseAuthentication();
// api.UseRouting();
// api.UseAuthorization();
api.UseErrorHandling();
api.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.Run();两次尝试都没有结果。
基于登录的令牌生成
帮助者阶级
public class JwtHelper
{
public static JwtSecurityToken GetJwtToken(
string username,
string signingKey,
string issuer,
string audience,
TimeSpan expiration,
Claim[] additionalClaims = null)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,username),
// this guarantees the token is unique
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
if (additionalClaims is object)
{
var claimList = new List<Claim>(claims);
claimList.AddRange(additionalClaims);
claims = claimList.ToArray();
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
return new JwtSecurityToken(
issuer: issuer,
audience: audience,
expires: DateTime.UtcNow.Add(expiration),
claims: claims,
signingCredentials: creds
);
}
}登录控制器方法
Guid userGid = await loginManager.LoginAsync(request.Email!, request.Password!);
if (userGid == default)
{
return base.NotFound();
}
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, userGid.ToString()));
claims.Add(new Claim(ClaimTypes.Name, userGid.ToString()));
string key = configuration["JwtSecurity:Key"];
string issuer = configuration["JwtSecurity:Issuer"];
string audience = configuration["JwtSecurity:Audience"];
string expiryDays = configuration["JwtSecurity:ExpiryDays"];
TimeSpan expiry = TimeSpan.FromDays(Convert.ToInt32(expiryDays));
var token = JwtHelper.GetJwtToken(
userGid.ToString(),
key,
issuer,
audience,
expiry,
claims.ToArray());
LoginResponse response = new(new JwtSecurityTokenHandler().WriteToken(token));
return base.Ok(response);令牌字符串响应存储在本地存储中。
客户端
向Http客户端添加标头
// GetTokenAsync retrieve the string from Local Storage
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"bearer",
(await _accessControlStateManager.GetTokenAsync())!.Write());中间件对接收令牌的服务器日志记录
public class JwtTokenHandlerMiddleware
{
readonly RequestDelegate _next;
readonly ILogger _logger;
public JwtTokenHandlerMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger(typeof(JwtTokenHandlerMiddleware).FullName!);
}
public async Task Invoke(HttpContext context)
{
JwtSecurityToken? jwt = context.GetJwtTokenFromAuthorizationHeader();
if (jwt != null)
{
_logger.LogInformation("Request received with token: {uri}", context.Request.GetDisplayUrl());
_logger.LogInformation("Token: {token}", jwt.Write());
}
else
{
_logger.LogInformation("Request received from ANONYMOUS: {uri}", context.Request.GetDisplayUrl());
}
await _next(context);
}
}
public static JwtSecurityToken? GetJwtTokenFromAuthorizationHeader(this HttpContext httpContext)
{
string text = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
return new JwtSecurityTokenHandler().ReadJwtToken(text);
}jwt.io输出

鉴于以上所述,似乎:
但我还是被授权了。
有什么建议吗?
试图隔离
禁用验证参数
试过:
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = false,
RequireExpirationTime = false,
ValidateLifetime = false
};并添加了简单的测试控制器:
using Microsoft.AspNetCore.Authorization;
namespace Cosmos.App.Server.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class TestController : ControllerBase
{
[HttpGet]
[Route("test")]
public async Task<IActionResult> TestAsync()
{
await Task.Delay(1);
return Ok("Hello");
}
}但同样的问题。
发布于 2022-09-22 20:02:43
找到了!
问题是,我将登录返回的字符串缓存为令牌,以便能够快速访问客户端中的声明(同时将从登录LocalStorage中收到的字符串保存起来)。
然后,当将令牌放在HttpClient上时,如果我有一个缓存的令牌,我就会将它写成字符串来填充Http请求上的授权。
问题在于从初始登录中接收到的字符串,例如,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJqdGkiOiI2MWYwYTRiMi0wNjQwLTRiMjgtYmM2Mi0zMDZlYTVmYmJiM2UiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijk0ZTZhZmE3LTQ0ZjAtNGU1NS04ODEzLTExNGY0ZjU5YTY3MiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJleHAiOjE2NjM5NjEwMjMsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0IiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3QifQ.5l9LRYIx3wXruW7BMa1DDbEoltVgP6Fbfkc2O03XAAY
在读取回缓存令牌时被截断。它似乎截断了我认为是签名密钥的内容。缺少了以下内容:
5l9LRYIx3wXruW7BMa1DDbEoltVgP6Fbfkc2O03XAAY
这意味着我将完整的字符串存储在本地存储中,但是没有签名密钥的缓存令牌。
然后,当使用缓存的令牌为我得到的授权头写入字符串时:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJqdGkiOiI2MWYwYTRiMi0wNjQwLTRiMjgtYmM2Mi0zMDZlYTVmYmJiM2UiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijk0ZTZhZmE3LTQ0ZjAtNGU1NS04ODEzLTExNGY0ZjU5YTY3MiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJleHAiOjE2NjM5NjEwMjMsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0IiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3QifQ.
没有的签名键的后缀。
这意味着对该字符串的验证在服务器上失败,即使jwt.io会很高兴地读取它。
使用本地存储中存储的完整字符串,而不是缓存令牌中的“书面”字符串解决了问题。
https://stackoverflow.com/questions/73817224
复制相似问题