SignalR 核心自定义身份验证 - 在 /negotiate 中对用户进行身份验证后,Context.User.Identity 为空
SignalR Core Custom Authentication - Context.User.Identity is null after user is authenticated in /negotiate
我为 SignalR Core 编写了自定义身份验证。其中一项功能是匿名登录。如果是第一次用户连接,它将创建新用户。代码有效,但问题是在 /myhub/negotiate 被清除并且 Context.User.Identity 中的所有声明被清除后完成的身份验证当客户端请求 /myhub/ 时,再次将 IsAuthenticated 更改为 false。只有在那之后 Context.User.Identity 中的声明才不会被清除。如果请求 /myhub/negotiate,我尝试 return 失败,但客户端不会向 /myhub/[=33= 发送请求] 如果我那样做。
知道如何解决或解决这个问题吗?我的自定义身份验证实现是否正确?
这是我正在使用的所有 class 的代码:
public class CustomAuthRequirementHandler : AuthorizationHandler<CustomAuthRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthRequirement requirement)
{
string name = context.User.Claims.Where(p => p.Type == ClaimTypes.NameIdentifier).Select(p => p.Value).SingleOrDefault();
if (!context.User.Identity.IsAuthenticated)
context.Fail();
else
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class CustomAuthRequirement : IAuthorizationRequirement
{
}
public class MyAuthenticationHandler : AuthenticationHandler<MyOptions>
{
public MyAuthenticationHandler(IOptionsMonitor<MyOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Context.User.Identity != null && Context.User.Identity.IsAuthenticated) return await Task.FromResult(
AuthenticateResult.Success(
new AuthenticationTicket(
new ClaimsPrincipal(Options.Identity),
new AuthenticationProperties(),
this.Scheme.Name)));
//if (Request.Path != "/myhub/") return await Task.FromResult(AuthenticateResult.Fail()); // only do authentication in /myhub/
var u = CreateNewUser(); // connect to db create new user
var claims = new List<Claim>() { };
claims.Add(new Claim(ClaimTypes.Name, u.Id.ToString()));
claims.Add(new Claim(ClaimTypes.NameIdentifier, u.Id.ToString()));
Options.Identity = new ClaimsIdentity(claims, "Custom");
var user = new ClaimsPrincipal(Options.Identity);
Context.User = user;
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, new AuthenticationProperties(), this.Scheme.Name)));
}
}
public class MyOptions : AuthenticationSchemeOptions
{
public ClaimsIdentity Identity { get; set; }
public MyOptions()
{
}
}
ConfigureServices中的配置代码
services.AddSingleton<IAuthorizationHandler, CustomAuthRequirementHandler>();
services.AddAuthorization(p =>
{
p.AddPolicy("MainPolicy", builder =>
{
builder.Requirements.Add(new CustomAuthRequirement());
builder.AuthenticationSchemes = new List<string> { "MyScheme" };
});
});
services.AddAuthentication(o =>
{
o.DefaultScheme = "MyScheme";
}).AddScheme<MyOptions, MyAuthenticationHandler>("MyScheme", "MyScheme", p => { });
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
配置代码
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSignalR(routes =>
{
routes.MapHub<Hubs.MainHub>("/main");
});
app.UseMvc();
}
编辑:添加了客户端代码
@page
@{
ViewData["Title"] = "Home page";
}
<input type="button" onclick="anonLogin()" value="AnonLogin" />
<script src="~/@@aspnet/signalr/dist/browser/signalr.js"></script>
<script type="text/javascript">
var connection;
function anonLogin() {
var token = "anon";
connection = new signalR.HubConnectionBuilder().withUrl("/main?anon=" + token).build();
connection.start().then(function () {
console.log("Connection ok");
console.log("Sending message....");
connection.invoke("Test").catch(function (err) {
return console.error("Error sending message: " + err.toString());
});
}).catch(function (err) {
console.log("Connection error: " + err.toString());
return console.error(err.toString());
});
}
</script>
我最终只为调用 /myhub/negotiate 创建了一个伪造的身份声明,因为这个调用并不重要,它只需要身份验证成功,这样它就可以转到 /myhub/。
var u = new DomainUser() { Id = -1 };
var claims = new List<Claim>() { };
claims.Add(new Claim(ClaimTypes.Name, u.Id.ToString()));
claims.Add(new Claim(ClaimTypes.NameIdentifier, u.Id.ToString()));
Options.Identity = new ClaimsIdentity(claims, "Custom");
var user = new ClaimsPrincipal(Options.Identity);
Context.User = user;
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), this.Scheme.Name)));
我为 SignalR Core 编写了自定义身份验证。其中一项功能是匿名登录。如果是第一次用户连接,它将创建新用户。代码有效,但问题是在 /myhub/negotiate 被清除并且 Context.User.Identity 中的所有声明被清除后完成的身份验证当客户端请求 /myhub/ 时,再次将 IsAuthenticated 更改为 false。只有在那之后 Context.User.Identity 中的声明才不会被清除。如果请求 /myhub/negotiate,我尝试 return 失败,但客户端不会向 /myhub/[=33= 发送请求] 如果我那样做。
知道如何解决或解决这个问题吗?我的自定义身份验证实现是否正确?
这是我正在使用的所有 class 的代码:
public class CustomAuthRequirementHandler : AuthorizationHandler<CustomAuthRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthRequirement requirement)
{
string name = context.User.Claims.Where(p => p.Type == ClaimTypes.NameIdentifier).Select(p => p.Value).SingleOrDefault();
if (!context.User.Identity.IsAuthenticated)
context.Fail();
else
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class CustomAuthRequirement : IAuthorizationRequirement
{
}
public class MyAuthenticationHandler : AuthenticationHandler<MyOptions>
{
public MyAuthenticationHandler(IOptionsMonitor<MyOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Context.User.Identity != null && Context.User.Identity.IsAuthenticated) return await Task.FromResult(
AuthenticateResult.Success(
new AuthenticationTicket(
new ClaimsPrincipal(Options.Identity),
new AuthenticationProperties(),
this.Scheme.Name)));
//if (Request.Path != "/myhub/") return await Task.FromResult(AuthenticateResult.Fail()); // only do authentication in /myhub/
var u = CreateNewUser(); // connect to db create new user
var claims = new List<Claim>() { };
claims.Add(new Claim(ClaimTypes.Name, u.Id.ToString()));
claims.Add(new Claim(ClaimTypes.NameIdentifier, u.Id.ToString()));
Options.Identity = new ClaimsIdentity(claims, "Custom");
var user = new ClaimsPrincipal(Options.Identity);
Context.User = user;
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, new AuthenticationProperties(), this.Scheme.Name)));
}
}
public class MyOptions : AuthenticationSchemeOptions
{
public ClaimsIdentity Identity { get; set; }
public MyOptions()
{
}
}
ConfigureServices中的配置代码
services.AddSingleton<IAuthorizationHandler, CustomAuthRequirementHandler>();
services.AddAuthorization(p =>
{
p.AddPolicy("MainPolicy", builder =>
{
builder.Requirements.Add(new CustomAuthRequirement());
builder.AuthenticationSchemes = new List<string> { "MyScheme" };
});
});
services.AddAuthentication(o =>
{
o.DefaultScheme = "MyScheme";
}).AddScheme<MyOptions, MyAuthenticationHandler>("MyScheme", "MyScheme", p => { });
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
配置代码
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSignalR(routes =>
{
routes.MapHub<Hubs.MainHub>("/main");
});
app.UseMvc();
}
编辑:添加了客户端代码
@page
@{
ViewData["Title"] = "Home page";
}
<input type="button" onclick="anonLogin()" value="AnonLogin" />
<script src="~/@@aspnet/signalr/dist/browser/signalr.js"></script>
<script type="text/javascript">
var connection;
function anonLogin() {
var token = "anon";
connection = new signalR.HubConnectionBuilder().withUrl("/main?anon=" + token).build();
connection.start().then(function () {
console.log("Connection ok");
console.log("Sending message....");
connection.invoke("Test").catch(function (err) {
return console.error("Error sending message: " + err.toString());
});
}).catch(function (err) {
console.log("Connection error: " + err.toString());
return console.error(err.toString());
});
}
</script>
我最终只为调用 /myhub/negotiate 创建了一个伪造的身份声明,因为这个调用并不重要,它只需要身份验证成功,这样它就可以转到 /myhub/。
var u = new DomainUser() { Id = -1 };
var claims = new List<Claim>() { };
claims.Add(new Claim(ClaimTypes.Name, u.Id.ToString()));
claims.Add(new Claim(ClaimTypes.NameIdentifier, u.Id.ToString()));
Options.Identity = new ClaimsIdentity(claims, "Custom");
var user = new ClaimsPrincipal(Options.Identity);
Context.User = user;
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), this.Scheme.Name)));