我有一个通过Azure AD进行身份验证的SPA(角度7)和一个API(.Net Core)。我正在使用adal-angular4将我的角度应用程序与AAD集成。
一切正常,但是我也将SignalR与API一起用作服务器,当我尝试从SPA连接时,我在协商“请求”中获得401未经授权,然后在响应标题中获得了此信息:>
该请求在Authorization标头中包含我的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);
}
}
这是我的角度客户端上的SignalRService。我正在app.component.ts的构造函数中运行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也会停止处理常规请求,并且我得到了回报:
我已经用IssuerSigningKey
填充了TokenValidationParameters
中的new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
属性。我在这里做错什么了吗?
/ EDIT
当API否则接受SignalR为什么不接受我的访问令牌?
答案 0 :(得分:1)
只需看看官方的docs。您需要对JWT Bearer事件进行特殊处理,以便身份验证有效。令牌需要转发到集线器。 看看我说过的部分
缺少该零件
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.
}
答案 1 :(得分:1)
在验证访问令牌的签名时,您应该获取公共密钥,因为Azure AD可以使用一组特定的公共-私有密钥对中的任何一个对令牌进行签名,可以在以下位置找到密钥:
https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration
在JSON响应中,您将看到属性jwks_uri
,该属性是URI,其中包含Azure AD的JSON Web密钥集。匹配jwt令牌中的kid
声明,您可以找到AAD用于通过非对称加密算法(默认情况下为RSA 256)对令牌进行签名的密钥。
在asp.net核心api中,当验证Azure AD发出的访问令牌时,可以使用AddJwtBearer
扩展名并提供正确的Authority
,以便中间件将正确地从Azure获取密钥。 AD OpenID配置终结点:
options.Authority = "https://login.microsoftonline.com/yourtenant.onmicrosoft.com/"
另一种选择是使用AddAzureADBearer
库中的Microsoft.AspNetCore.Authentication.AzureAD.UI
扩展名。您还应该设置正确的authority(instance + domain)
,中间件将根据您的配置帮助验证签名和声明。
答案 2 :(得分:0)
将中心上的Authorize属性更改为
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]