ASP.NET Core SignalR 返回 401 Unauthorized using Azure AD [英] ASP.NET Core SignalR returns 401 Unauthorized using Azure AD
问题描述
我有一个 SPA(angular 7)和一个 API(.Net Core),我使用 Azure AD 对其进行了身份验证.我正在使用
请求在 Authorization 标头中包含我的不记名令牌,当我通过
我已使用 new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
填充 TokenValidationParameters
中的 IssuerSigningKey
属性.我在这里做错了什么吗?
/EDIT
为什么在 API 接受我的访问令牌时 SignalR 不接受它?
在验证访问令牌的签名时,您应该获取公钥,因为 Azure AD 可能会使用一组特定的公钥-私钥对中的任何一个来对令牌进行签名,可以在以下位置找到密钥:
https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration
在 JSON 响应中,您将看到一个属性 jwks_uri
,它是包含 Azure AD 的 JSON Web 密钥集的 URI.匹配 jwt token 中的 kid
声明,您可以找到 AAD 使用非对称加密算法对令牌进行签名的密钥,例如默认的 RSA 256.
在asp.net core apis中,在验证Azure AD颁发的访问令牌时,可以使用AddJwtBearer
扩展并提供正确的Authority
,这样中间件就会从 Azure AD OpenID 配置端点正确获取密钥:
options.Authority = "https://login.microsoftonline.com/yourtenant.onmicrosoft.com/"
另一种选择是使用 Microsoft.AspNetCore.Authentication.AzureAD.UI
库中的 AddAzureADBearer
扩展.您还应该设置正确的 authority(instance + domain)
,中间件将根据您的配置帮助验证签名和声明.
I have a SPA (angular 7) and an API (.Net Core) which I authenticate with Azure AD. I'm using adal-angular4 to integrate my angular application with AAD.
Everything works great, but I'm also using SignalR with the API as server and when I try to connect from my SPA I get 401 Unauthorized on the negotiate "request" and I get this back in the Response Headers:
The request contains my Bearer token in the Authorization header, and when I run the token through jwt.io, I can see that the "aud" value is the Azure AD ClientId for my SPA.
All regular request to the API contains the same token and I have no issues with those. I have [Authorize] on all my Controllers and on my Hub, but it's only the SignalR Hub that causes this issue.
My server Startup:
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
_env = env;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment _env;
public void ConfigureServices(IServiceCollection services)
{
StartupHandler.SetupDbContext(services, Configuration.GetConnectionString("DevDb"));
// Setup Authentication
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options =>
{
Configuration.Bind("AzureAD", options);
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add functionality to inject IOptions<T>
services.AddOptions();
// Add AzureAD object so it can be injected
services.Configure<AzureAdConfig>(Configuration.GetSection("AzureAd"));
services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
options.KeepAliveInterval = TimeSpan.FromSeconds(10);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
app.UseHsts();
}
app.UseCookiePolicy();
app.UseHttpsRedirection();
//app.UseCors("AllowAllOrigins");
app.UseCors(builder =>
{
builder.AllowAnyOrigin();
builder.AllowAnyMethod().AllowAnyHeader();
builder.AllowCredentials();
});
app.UseAuthentication();
app.UseSignalR(routes => routes.MapHub<MainHub>("/mainhub"));
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(_env.ContentRootPath, "Files")),
RequestPath = new PathString("/Files")
});
app.UseMvc();
}
My SignalR Hub:
[Authorize]
public class MainHub : Hub
{
private readonly IEntityDbContext _ctx;
public MainHub(IEntityDbContext ctx)
{
_ctx = ctx;
_signalRService = signalRService;
}
public override Task OnConnectedAsync()
{
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
return base.OnDisconnectedAsync(exception);
}
}
And this is my SignalRService on my angular client. I'm running startConnection() in the constructor of app.component.ts.
export class SignalRService {
private hubConnection: signalR.HubConnection;
constructor(private adal: AdalService) {}
startConnection(): void {
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(AppConstants.SignalRUrl, { accessTokenFactory: () => this.adal.userInfo.token})
.build();
this.hubConnection.serverTimeoutInMilliseconds = 60000;
this.hubConnection.on('userConnected', (user) =>
{
console.log(user);
});
this.hubConnection.start()
.then(() => console.log('Connection started'))
.catch(err =>
{
console.log('Error while starting connection: ' + err);
});
}
}
I have tried this solution, but I can't get that to work either.
Edit
When I've implemented the solution from the official docs, the API stops working on regular requests as well and I get back:
I've populate the IssuerSigningKey
property in TokenValidationParameters
with new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
. Am I doing anything wrong here?
/EDIT
Why won't SignalR accept my accesstoken when the API otherwise accept it?
When validating the signature of access token , you should get the public key since Azure AD may sign token using any one of a certain set of public-private key pairs , the keys could be found at :
https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration
Within the JSON response, you’ll see a property jwks_uri
which is the URI that contains the JSON Web Key Set for Azure AD. Matching the kid
claim in jwt token , you can find the key which AAD used to sign the token with asymmetric encryption algorithms, such as RSA 256 by default .
In asp.net core apis , when validating the access token which issued by Azure AD , you can use AddJwtBearer
extension and provide the correct Authority
, so that middleware will correctly get the keys from Azure AD OpenID configuration endpoint :
options.Authority = "https://login.microsoftonline.com/yourtenant.onmicrosoft.com/"
Another choice is to use AddAzureADBearer
extension from Microsoft.AspNetCore.Authentication.AzureAD.UI
library . You should also set correct authority(instance + domain)
, middleware will help validating the signature and claims based on your configuration .
这篇关于ASP.NET Core SignalR 返回 401 Unauthorized using Azure AD的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!