jwt - Blazor:重新加载页面时缺少授权标头
问题描述
我对 Blazor 应用程序中的授权标头有疑问。我设法登录并使用执行 api 请求的不同页面来获取一些数据,但是每当我想重新加载页面时就会出现问题。
当点击菜单中的链接并进入“ https://localhost:44387/MyExerciseTemplateBanks/ ”时,将获取并显示数据(如果我单击另一个页面并返回):
:authority: localhost:44387
:method: GET
:path: /api/ExerciseTemplateBanks
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: fr-BE,fr;q=0.9,en-BE;q=0.8,en;q=0.7,fr-FR;q=0.6,en-US;q=0.5
authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNGMwNWY2ZC05M2Q0LTRiMDctODJjZS03ZWZiYzUyZTEwMGUiLCJqdGkiOiIwYTY5NzQ1NS1kNjBlLTQwYzMtODI0YS1lYWNkMjc5NTI5YmMiLCJ1bmlxdWVfbmFtZSI6Ik1yQXRvbWUiLCJyb2xlIjpbIkR1bW15IiwiVEVBQ0hFUiJdLCJQZXJtaXNzaW9ucyI6IlxuXHUwMDBiXGZcclx1MDAwZVx1MDAwZlx1MDAxMFx1MDAxMVx1MDAxMlx1MDAxM1x1MDAxNFx1MDAxNVx1MDAxNlx1MDAxN1x1MDAxOFx1MDAxOVx1MDAxYVx1MDAxYlx1MDAxY1x1MDAxZFx1MDAxZVx1MDAxZiAhXCIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD1kaW90eSIsImV4cCI6MTU3NzUyNjM0NywiaXNzIjoibG9jYWxob3N0IiwiYXVkIjoidXNlcnMifQ.FEvwzEoR868oINGVdyedsA8z3Qlx16ENVvvLkRxSIEw
cookie: .AspNetCore.Identity.Application=CfDJ8P4mU_JbFqRCn54otwf12q5yA7N68HaG-bobWYp3vyOztrMZVddJea_LhUAXKRcVKyGD3eATQrfSqtp7_ruRNrtujaFHdGJYTfE-RsOGmm9MschC_eTNzJfC13U4J7IlxNgO6J578tm_3DG8WnmxFjg2H7qQJJm1IYHt8OY49TqFpjCWpvQVkiAvf4iUmjO6CXJrRWwYQoDM65NRXtJtbcVE0zzA12r2E15WLMMOb6DMGwMpGA5DsU1zuXMY83f0ZBUmlnXBb4xYbPSICwKX-RxAnSJZYp7dYfHmx08_fbnQzQ_1FgMGcZSrf_TxRnFuxMh6o1YYMxkgnScZ4sWiIJIfFBIEJdXuyUKjRyEKH8vczojfFsGLDLoCcBSumSRMxAWV_2wm6rHT6OhEQLlYFWwlJdlqgMz0ZzwcVnqijkznystakHngxQNdJjjVVrt9uQwNi1SaOl2pvh0g0RqjPT0jbkU5BeO7XJ_pWRNK7bs2G4LMaFSt14M45-PerDKD6nBV6XS_7he3oEZMw49PSjHjQnc6BlKhT_TGj6RrP3RotEz61JSQU8wiCtTpLrGouD-f4FtThQsO4oHCV6r6HzD2WGo9oGm_qbsRnIlB16NbseqsAV-7TVuF2fMfboSVhZj7YFzHDNGLfTqK4yIjkU65qWRHsdPoR5WBLuStBAW1QC1-7nj8H_NjXfEuOhPyzUKR4usBXbYBr6a_PtkGS0I
referer: https://localhost:44387/MyExerciseTemplateBanks/
sec-fetch-mode: cors
sec-fetch-site: same-origin
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
但是只要我在浏览器中输入路径或刷新页面,请求中的授权标头就会丢失:
:method: GET
:path: /api/ExerciseTemplateBanks
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: fr-BE,fr;q=0.9,en-BE;q=0.8,en;q=0.7,fr-FR;q=0.6,en-US;q=0.5
cookie: .AspNetCore.Identity.Application=CfDJ8P4mU_JbFqRCn54otwf12q5yA7N68HaG-bobWYp3vyOztrMZVddJea_LhUAXKRcVKyGD3eATQrfSqtp7_ruRNrtujaFHdGJYTfE-RsOGmm9MschC_eTNzJfC13U4J7IlxNgO6J578tm_3DG8WnmxFjg2H7qQJJm1IYHt8OY49TqFpjCWpvQVkiAvf4iUmjO6CXJrRWwYQoDM65NRXtJtbcVE0zzA12r2E15WLMMOb6DMGwMpGA5DsU1zuXMY83f0ZBUmlnXBb4xYbPSICwKX-RxAnSJZYp7dYfHmx08_fbnQzQ_1FgMGcZSrf_TxRnFuxMh6o1YYMxkgnScZ4sWiIJIfFBIEJdXuyUKjRyEKH8vczojfFsGLDLoCcBSumSRMxAWV_2wm6rHT6OhEQLlYFWwlJdlqgMz0ZzwcVnqijkznystakHngxQNdJjjVVrt9uQwNi1SaOl2pvh0g0RqjPT0jbkU5BeO7XJ_pWRNK7bs2G4LMaFSt14M45-PerDKD6nBV6XS_7he3oEZMw49PSjHjQnc6BlKhT_TGj6RrP3RotEz61JSQU8wiCtTpLrGouD-f4FtThQsO4oHCV6r6HzD2WGo9oGm_qbsRnIlB16NbseqsAV-7TVuF2fMfboSVhZj7YFzHDNGLfTqK4yIjkU65qWRHsdPoR5WBLuStBAW1QC1-7nj8H_NjXfEuOhPyzUKR4usBXbYBr6a_PtkGS0I
referer: https://localhost:44387/MyExerciseTemplateBanks/
sec-fetch-mode: cors
sec-fetch-site: same-origin
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
我的第一个猜测是有一些与状态管理相关的东西,但是 localStorage 应该通过重新加载来持续存在,对吗?
基本上,我的问题是: 1. 刷新页面时 blazor 客户端发生了什么 2. 在刷新使用 API 的页面时,如何确保授权标头出现在请求中?
这是我的项目中与身份验证和授权相关的代码:
提交登录表单时调用AuthService.Login() :
public async Task<LoginResult> Login(LoginModel loginModel)
{
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Accounts/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorage.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginResult.Token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token);
return loginResult;
}
ApiAuthenticationStateProvider :
public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
public void MarkUserAsAuthenticated(string token)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
帐户控制器:
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginDto loginDto) //Former CreateToken (Renamed)
{
var user = await _userManager.FindByNameAsync(loginDto.Username);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, false);
if (result.Succeeded)
{
//Create the token
var claims = new List<Claim>()
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
};
var userRoles = await _userManager.GetRolesAsync(user);
foreach (var userRole in userRoles)
{
claims.Add(new Claim("role", userRole));
var role = await _roleManager.FindByNameAsync(userRole);
if (role == null)
{
continue;
}
var roleClaims = await _roleManager.GetClaimsAsync(role);
foreach (Claim roleClaim in roleClaims)
{
claims.Add(roleClaim);
}
}
//
//Calculate the Permissions Claim value and add it
claims.Add(new Claim("Permissions", await _rtoPCalcer.CalcPermissionsForUser(user, userRoles)));
//
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"])); //
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_config["Tokens:Issuer"],
_config["Tokens:Audience"],
claims,
expires: DateTime.UtcNow.AddMinutes(30),
signingCredentials: credentials
);
DateTime expiration = token.ValidTo;
return Ok(new LoginResult { Successful = true, Token = new JwtSecurityTokenHandler().WriteToken(token), ValidTo = expiration });
}
}
return BadRequest(new LoginResult { Successful = false, Error = "Username and password are invalid." });
}
练习模板银行.razor:
@using System.Net.Http;
@using ViewModels.ExerciseModule
@inject HttpClient Http
@page "/MyExerciseTemplateBanks"
<h3>ExerciseTemplateBanks</h3>
<p>This component demonstrates fetching data from the server.</p>
@if (ApiResponse == null)
{
<p><em>Loading...</em></p>
}
else
{
if (ApiResponse != null && ApiResponse.Results.Any())
foreach (var item in ApiResponse.Results)
{
<div class="card" style="width: 18rem;">
<div class="card-body">
<h3 class="card-title">Name : @item.Name</h3>
<p class="card-text">Text : Bla bla bla</p>
</div>
</div>
}
<div class="card" style="width: 18rem;">
<div class="card-body">
<p class="card-text"><a href="/AddExerciseTemplateBank">Create New Bank</a></p>
</div>
</div>
}
@code {
private string Response { get; set; }
private WebApiMessageAndResult<List<ExerciseTemplateBankVm>> ApiResponse { get; set; }
private List<ExerciseTemplateBankVm> Banks { get; set; }
protected override async Task OnInitializedAsync()
{
ApiResponse = await Http.GetJsonAsync<WebApiMessageAndResult<List<ExerciseTemplateBankVm>>>("api/ExerciseTemplateBanks");
}
按照 Enet 的要求(参见下面的评论):我尝试添加这两行代码。
var token = await localStorage.GetItemAsync<string>("authToken");
Http.DefaultRequestHeaders.Authorization = new authenticationHeaderValue("bearer", token);
但只需添加第一个和 Console.Writeline(token) 并重新加载页面即可开始工作(存在授权标头)。
var token = await _localStorage.GetItemAsync<string>("authToken");
ApiResponse = await Http.GetJsonAsync<WebApiMessageAndResult<List<ExerciseTemplateBankVm>>>("api/ExerciseTemplateBanks");
这是否意味着,我必须在每次 Api 请求之前从标头中检索令牌以确保重新加载页面能够正常工作?想要一个解释,有点迷失在这里:o
解决方案
从实现中设置Http.DefaultRequestHeaders.Authorization
构造函数
AuthenticationStateProvider
为我解决了这个问题。
推荐阅读
- docker - 哪个 dockerized 应用程序的谷歌服务不需要 http 请求?
- python - 在熊猫中转置几列(不是整个数据框)(与get_dummies相反)
- sql-server - 在 Microsoft SQL Server Management Studio 17 中创建视图返回错误(但查询本身有效)
- java - ConcurrentModificationException:如果它不是第一个变量,为什么删除 List 中的 null 会引发此异常
- python - AWS Glue Python shell 配置 DPU
- python - 在openpyxl条件格式公式中使用多个单元格
- c# - Visual Studio Code 没有突出显示我的代码中的编译错误
- python-3.x - IndentationError:创建python3 virtualenv时出现意外缩进
- javascript - 使用javascript将数组数据循环到对象中
- python - 为 100 名学生输入分数而不制作 100 个输入框