使用 Cookie 进行 Blazor 服务器端身份验证
Posted
技术标签:
【中文标题】使用 Cookie 进行 Blazor 服务器端身份验证【英文标题】:Blazor-Server side authentication with Cookie 【发布时间】:2021-12-05 17:40:51 【问题描述】:我正在尝试在 Blazor-Server 端应用程序上实现针对 LDAP 服务器的简单登录,并使用 cookie 来存储用户声明。我将 MainLayout 设置为 Authorized,如果用户未通过身份验证,它将被重定向到登录页面。我已经测试了 LDAP 连接并且它工作正常,问题是无论我做什么,cookie 都不会在浏览器中创建。当我运行 POST 命令时,我看到 HttpStatusCode.OK 但它没有创建 cookie,浏览器当然会再次重定向到登录页面。
谁能告诉我我做错了什么?我的代码:
Startup.cs
public void ConfigureServices(IServiceCollection services)
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
);
AuthenticationController.cs
[ApiController]
public class AuthenticationController : Controller
[HttpPost]
[Route("authentication/login")]
public async Task<ActionResult> Login([FromBody]UserCredentials credentials)
string path = "LDAP://serveraddress.xxx";
try
using DirectoryEntry entry = new(path, credentials.Username, credentials.Password);
using DirectorySearcher searcher = new(entry);
searcher.Filter = $"(&(objectclass=user)(objectcategory=person)(samaccountname=credentials.Username))";
var result = searcher.FindOne();
if (result != null)
List<Claim> claims = new();
claims.Add(new Claim(ClaimTypes.Name, credentials.Username));
//Get Groups
ResultPropertyCollection fields = result.Properties;
foreach (var group in result.Properties["memberof"])
var distinguishedName = new X500DistinguishedName(group.ToString());
var commonNameData = new AsnEncodedData("CN", distinguishedName.RawData);
var commonName = commonNameData.Format(false);
if (!string.IsNullOrEmpty(commonName))
claims.Add(new Claim(ClaimTypes.Role, commonName));
//Get Emails
foreach (var email in result.Properties["mail"])
claims.Add(new Claim(ClaimTypes.Email, email.ToString()));
ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new()
AllowRefresh = true,
IssuedUtc = DateTime.Now,
ExpiresUtc = DateTimeOffset.Now.AddDays(1),
IsPersistent = true,
;
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
return Ok();
else
return NotFound("User Not Found!");
catch (Exception)
return NotFound("Login credentials is incorrect!");
[HttpPost]
[Route("authentication/logout")]
public async Task<IActionResult> Logout()
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Ok();
Login.razor
@page "/login"
@page "/login/ErrorMessage"
@layout CenteredBlockLayout
@attribute [AllowAnonymous]
<MudPaper Elevation="25" Class="pa-8" Width="100%" MaxWidth="500px">
<MudItem><img src="/images/logo.svg" style="width:400px; height:50px;" /></MudItem>
<MudText Typo="Typo.h4" GutterBottom="true">Sign In</MudText>
<MudTextField @bind-Value="@Username" T="string" Label="Username"/>
<MudTextField @bind-Value="@Password" T="string" Label="Password"/>
<MudButton OnClick="(() => PerformLoginAsync())">Sign In</MudButton>
</MudPaper>
@if (!string.IsNullOrEmpty(ErrorMessage))
<MudAlert Severity="Severity.Error">@ErrorMessage</MudAlert>
Login.razor.cs
public partial class Login
public string Username get; set;
public string Password get; set;
[Parameter]
public string ErrorMessage get; set;
[Inject]
HttpClient Client get; set;
[Inject]
private NavigationManager NavMan get; set;
private async Task PerformLoginAsync()
if (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password))
UserCredentials cred = new UserCredentials
Username = Username,
Password = Password
;
var serialized = JsonConvert.SerializeObject(cred);
var stringContent = new StringContent(serialized, Encoding.UTF8, "application/json");
using var result = await Client.PostAsync($"NavMan.BaseUriauthentication/login", stringContent);
if (result.StatusCode == System.Net.HttpStatusCode.OK)
NavMan.NavigateTo("/", true);
else
ErrorMessage = await result.Content.ReadAsStringAsync();
【问题讨论】:
有人可以帮忙吗? 【参考方案1】:我相信您需要将 cookie 附加到响应中。我没有用你的代码对此进行测试,但它应该像这样工作:
HttpContext.Response.Cookies.Append("my_cookie", claimsString, new CookieOptions()
Domain = "mydomain.com",
SameSite = SameSiteMode.Lax,
Secure = true,
Path = "/",
Expires = DateTime.UtcNow.AddDays(1)
(当然,这些 cookie 选项只是一个示例。根据您的特定需求定制它们。)
请记住,您需要将声明转换为字符串,以便将其作为值存储在 cookie 中。在我们的例子中,我们将声明存储在 JWT 中,这就是存储在 cookie 中的内容。这是我的做法:
public string CreateJWT(HttpContext httpContext, User user)
var handler = new JwtSecurityTokenHandler();
var descriptor = new SecurityTokenDescriptor
Subject = new ClaimsIdentity(new Claim[]
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.Name, $"user.FirstName user.LastName"),
new Claim(ClaimTypes.Email, user.Email),
),
Expires = DateTime.UtcNow.AddMinutes(Config.AccessExpMins),
Issuer = Config.Issuer,
Audience = Config.Audience,
SigningCredentials = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256)
;
var token = handler.CreateJwtSecurityToken(descriptor);
var accessToken = handler.WriteToken(token);
httpContext.Response.Cookies.Append("my_cookie", accessToken, new CookieOptions()
Domain = Config.CookieDomain,
SameSite = SameSiteMode.Lax,
Secure = true,
Path = "/",
Expires = DateTime.UtcNow.AddMinutes(Config.AccessExpMins)
);
return accessToken;
至于解析 JWT,我相信有很多方法可以解决。对我有用的是this one。
【讨论】:
您好,首先非常感谢您的帮助!我想问,我是否需要在 SignInAsync() 和 Ok() 之间的控制器代码中附加 cookie?或者我需要在发布请求后在登录页面中执行此操作吗?您最终是否有使用 JWT 存储/检索声明的示例? 乐于助人!我的建议是在 SignInAsync() 和 Ok() 之间进行。我已经更新了我的答案,包括我自己创建 JWT 的示例,以及对解析声明的方法的引用。 哦,我明白了..所以 JWT 的工作方式是用户在登录屏幕中键入 user/pwd,信息被发送到控制器,如果数据正确,则生成带有声明的令牌并返回到登录页面。然后我需要解析令牌,获取所有声明并将它们添加到 Blazor 身份服务,以告知应用用户已通过身份验证并且拥有 X 数量的声明。 :) 是的,你已经明白了。为了增加安全性,您甚至可以在前端验证令牌签名,前提是您使用的是非对称密钥签名。一切尽在你想如何实现它。不管怎样,虽然我希望这应该给你你所需要的。以上是关于使用 Cookie 进行 Blazor 服务器端身份验证的主要内容,如果未能解决你的问题,请参考以下文章
如何在没有 Microsoft 身份的情况下对 blazor 服务器进行 jwt 身份验证?