ASP.NET Core SignalR returns 401 未授权使用 Azure AD
ASP.NET Core SignalR returns 401 Unauthorized using Azure AD
我有一个 SPA (angular 7) 和一个 API (.Net Core),我使用 Azure AD 对其进行了身份验证。我正在使用 adal-angular4 将我的 angular 应用程序与 AAD 集成。
一切正常,但我也在使用带有 API 的 SignalR 作为服务器,当我尝试从我的 SPA 连接时,我在协商 "request" 时收到 401 Unauthorized 并且我得到了这个返回响应 Headers:
请求在授权header中包含我的Bearer令牌,当我通过jwt.io运行令牌时,我可以看到"aud"值是我的 SPA 的 Azure AD ClientId。
对 API 的所有常规请求都包含相同的标记,我对这些没有任何问题。我的所有控制器和集线器上都有 [授权],但只有 SignalR 集线器导致了这个问题。
我的服务器启动:
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();
}
我的 SignalR 中心:
[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);
}
}
这是我 angular 客户端上的 SignalRService。我在 app.component.ts.
的构造函数中 运行ning startConnection()
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);
});
}
}
我试过 this 解决方案,但我也无法使用它。
编辑
当我从官方文档实施解决方案时,API 也停止处理常规请求,我回来了:
我用 new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
填充了 TokenValidationParameters
中的 IssuerSigningKey
属性。我做错了什么吗?
/编辑
为什么 SignalR 不接受我的访问令牌,而 API 否则接受它?
看看官方就知道了docs。您需要对 JWT Bearer 事件进行特殊处理,以便您的身份验证正常工作。令牌需要转发到集线器。
看看我说的部分
THAT PART IS MISSING
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure JWT Bearer Auth to expect our security key
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
//THAT IS THE PART WHICH IS MISSING IN YOUR CONFIG !
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
// Change to use Name as the user identifier for SignalR
// WARNING: This requires that the source of your JWT token
// ensures that the Name claim is unique!
// If the Name claim isn't unique, users could receive messages
// intended for a different user!
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// Change to use email as the user identifier for SignalR
// services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// WARNING: use *either* the NameUserIdProvider *or* the
// EmailBasedUserIdProvider, but do not use both.
}
将集线器上的授权属性更改为
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
验证访问令牌的签名时,您应该获得 public 密钥,因为 Azure AD 可能使用一组特定的 public-私钥对中的任何一个来签署令牌,密钥可以位于:
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)
,中间件将根据您的配置帮助验证签名和声明。
我有一个 SPA (angular 7) 和一个 API (.Net Core),我使用 Azure AD 对其进行了身份验证。我正在使用 adal-angular4 将我的 angular 应用程序与 AAD 集成。
一切正常,但我也在使用带有 API 的 SignalR 作为服务器,当我尝试从我的 SPA 连接时,我在协商 "request" 时收到 401 Unauthorized 并且我得到了这个返回响应 Headers:
请求在授权header中包含我的Bearer令牌,当我通过jwt.io运行令牌时,我可以看到"aud"值是我的 SPA 的 Azure AD ClientId。
对 API 的所有常规请求都包含相同的标记,我对这些没有任何问题。我的所有控制器和集线器上都有 [授权],但只有 SignalR 集线器导致了这个问题。
我的服务器启动:
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();
}
我的 SignalR 中心:
[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);
}
}
这是我 angular 客户端上的 SignalRService。我在 app.component.ts.
的构造函数中 运行ning startConnection()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);
});
}
}
我试过 this 解决方案,但我也无法使用它。
编辑
当我从官方文档实施解决方案时,API 也停止处理常规请求,我回来了:
我用 new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
填充了 TokenValidationParameters
中的 IssuerSigningKey
属性。我做错了什么吗?
/编辑
为什么 SignalR 不接受我的访问令牌,而 API 否则接受它?
看看官方就知道了docs。您需要对 JWT Bearer 事件进行特殊处理,以便您的身份验证正常工作。令牌需要转发到集线器。 看看我说的部分
THAT PART IS MISSING
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure JWT Bearer Auth to expect our security key
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
//THAT IS THE PART WHICH IS MISSING IN YOUR CONFIG !
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
// Change to use Name as the user identifier for SignalR
// WARNING: This requires that the source of your JWT token
// ensures that the Name claim is unique!
// If the Name claim isn't unique, users could receive messages
// intended for a different user!
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// Change to use email as the user identifier for SignalR
// services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// WARNING: use *either* the NameUserIdProvider *or* the
// EmailBasedUserIdProvider, but do not use both.
}
将集线器上的授权属性更改为
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
验证访问令牌的签名时,您应该获得 public 密钥,因为 Azure AD 可能使用一组特定的 public-私钥对中的任何一个来签署令牌,密钥可以位于:
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)
,中间件将根据您的配置帮助验证签名和声明。