我有一个带有oidc身份验证的Blazor WebAssembly(最新3.2.0(应用程序。asp.net身份验证提供了一种获取accessToken的方法,但看不到任何访问我的场景所需的id_token(jwt(的方法。我可以在浏览器的本地存储中看到id_token。访问它的最佳方式是什么?
感谢
您可以使用JSInterop从会话存储中读取它,它存储在密钥oidc.user:{app baseUri}:{app client id}:
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
...
@code {
private async Task<string> ReadIdToken()
{
const string clientId = "your oidc client id";
var userDataKey = $"oidc.user:{NavigationManager.BaseUri}:{clientId}";
var userData = await JSRuntime.InvokeAsync<UserData>("sessionStorage.getItem", userDataKey);
return userData.id_token;
}
class UserData
{
public string id_token { get; set; }
public int expires_at { get; set; }
}
}
这里有一个工作代码示例,允许您获得原始格式的id_token以及从中解析的声明列表。
注意:您应该先进行身份验证,然后才能看到结果。。。
@page "/"
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
@using System.Security.Claims
@using System.Text.Json
<p>@JwtToken</p>
@foreach (var claim in claims)
{
<p>@claim</p>
}
@code {
List<Claim> claims = new List<Claim>();
string JwtToken;
protected override async Task OnInitializedAsync()
{
await GetJwtToken();
}
private async Task GetJwtToken()
{
var baseUri = NavigationManager.BaseUri.Substring(0,
NavigationManager.BaseUri.Length - 1);
// client id example: RoleBasedApiAuthorization.Client
const string clientID = "<Place here your client id>";
var key = $"oidc.user:{baseUri}:{clientID}";
JwtToken = await JSRuntime.InvokeAsync<string>
("sessionStorage.getItem", key);
if (JwtToken != null)
{
claims = ParseClaimsFromJwt(JwtToken).ToList();
}
}
public IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
非常感谢大家-我已经为此头疼了一个星期了(doh-忘记查看Chrome中的浏览器会话数据来考虑使用JRRuntime…(
我不确定这是否是Cognito特定的,但对我来说,关键不是使用NavigationManager BaseUri,而是使用OIDC Authority。
@page "/"
@using System.Text.Json
@inject IJSRuntime JSRuntime
<AuthorizeView>
<Authorized>
<div>
<b>CachedAuthSettings</b>
<pre>
@JsonSerializer.Serialize(authSettings, indented);
</pre>
<br/>
<b>CognitoUser</b><br/>
<pre>
@JsonSerializer.Serialize(user, indented);
</pre>
</div>
</Authorized>
<NotAuthorized>
<div class="alert alert-warning" role="alert">
Everything requires you to <a href="/authentication/login">Log In</a> first.
</div>
</NotAuthorized>
</AuthorizeView>
@code {
JsonSerializerOptions indented = new JsonSerializerOptions() { WriteIndented = true };
CachedAuthSettings authSettings;
CognitoUser user;
protected override async Task OnInitializedAsync()
{
string key = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.CachedAuthSettings";
string authSettingsRAW = await JSRuntime.InvokeAsync<string>("sessionStorage.getItem", key);
authSettings = JsonSerializer.Deserialize<CachedAuthSettings>(authSettingsRAW);
string userRAW = await JSRuntime.InvokeAsync<string>("sessionStorage.getItem", authSettings?.OIDCUserKey);
user = JsonSerializer.Deserialize<CognitoUser>(userRAW);
}
public class CachedAuthSettings
{
public string authority { get; set; }
public string metadataUrl { get; set; }
public string client_id { get; set; }
public string[] defaultScopes { get; set; }
public string redirect_uri { get; set; }
public string post_logout_redirect_uri { get; set; }
public string response_type { get; set; }
public string response_mode { get; set; }
public string scope { get; set; }
public string OIDCUserKey => $"oidc.user:{authority}:{client_id}";
}
public class CognitoUser
{
public string id_token { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string token_type { get; set; }
public string scope { get; set; }
public int expires_at { get; set; }
}
}
如果我直接尝试使用JSRuntme.InvokeAsync将字符串转换为类,就会出现序列化错误,但它与JsonSerializer配合使用很好,这就是为什么你会看到这一看似额外的步骤。
我终于可以使用我的Blazor 7.0 WASM应用程序了,该应用程序通过OIDC与谷歌进行身份验证,并且需要发送";ID令牌";到我的ASP.NET Web API。我花了很长时间才弄清楚这个问题。如上所述;访问令牌";是在";授权";默认情况下在中间件中使用头。相反,ASP.NET Web API需要JWT;ID令牌";。
正如其他响应者所提到的,这只能通过Javascript访问。我在Blazor应用程序中所做的是从AuthorizationHandler创建一个派生类,如下所示:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.JSInterop;
namespace ExperiencePlatform.Client.Infrastructure
{
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
private NavigationManager _navigation;
private IConfiguration _configuration;
private IJSRuntime _jsRuntime;
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation, IConfiguration configuration, IJSRuntime jsRuntime) : base(provider, navigation)
{
_jsRuntime = jsRuntime;
_configuration = configuration;
_navigation = navigation;
ConfigureHandler(
authorizedUrls: new[] { configuration[Constants.ExperiencePlatformServerKey] },
scopes: new[] { "openid" });
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string idToken = await _jsRuntime.InvokeAsync<string>("getOidToken", $"oidc.user:{_configuration["Google:Authority"]}:{_configuration["Google:ClientId"]}");
request.Headers.Add(ExperiencePlatform.Shared.Authentication.Constants.IdTokenHttpHeader, $"Bearer {idToken}");
return await base.SendAsync(request, cancellationToken);
}
}
}
";getOidToken";Javascript中的函数如下所示:
function getOidToken(key) {
var strJson = sessionStorage.getItem(key);
if (strJson != null) {
var json = JSON.parse(strJson);
return json.id_token;
}
return null;
}
请记住,在Blazor WASM的Program.cs中设置依赖项注入并挂钩到CustomAuthorizationMessageHandler类中,如下所示:
builder.Services.AddTransient<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient(Constants.ExperiencePlatformServerKey, client => client.BaseAddress = new Uri(config[Constants.ExperiencePlatformServerKey]))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient(Constants.ExperiencePlatformServerKey));
最后,在您的ASP.NET Web API端,假设您有一个定义了如下端点的控制器:
[ApiController]
[Authorize]
[Route("[controller]")]
public class ExperiencesController : ControllerBase {...}
在您的Program.cs中,请确保您处理";OnMessageReceived";JwtBearerEvent并获取自定义的Authorization标头。这里是完整的代码块:
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration["Google:Authority"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Google:Authority"],
ValidAudience = builder.Configuration["Google:ClientId"],
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
RequireExpirationTime = true,
ValidateIssuerSigningKey = true
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
string idTokenHeader = context.Request.Headers[ExperiencePlatform.Shared.Authentication.Constants.IdTokenHttpHeader];
if (!string.IsNullOrEmpty(idTokenHeader) && idTokenHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
string token = idTokenHeader.Substring("Bearer ".Length).Trim();
context.Token = token;
}
return Task.CompletedTask;
}
};
});
随之而来的是未按正确顺序定义中间件的常见嫌疑:
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseHttpsRedirection();
app.MapControllers();
app.Run();