首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用Cookie的Blazor服务器端身份验证

使用Cookie的Blazor服务器端身份验证
EN

Stack Overflow用户
提问于 2021-10-18 17:04:14
回答 1查看 397关注 0票数 1

我试图在Blazor-Server端应用程序上实现一个针对LDAP服务器的简单登录,并使用cookie存储用户声明。我已经将MainLayout设置为授权,如果用户没有经过身份验证,它将被重新定向到登录页面。我已经测试了LDAP连接,它正常工作,问题是无论我做什么,cookie都不会在浏览器中创建。当我运行POST命令时,我会看到HttpStatusCode.OK,但是它没有创建,浏览器当然会重新定向到登录页面。

有人能告诉我我做错了什么吗?我的代码:

Startup.cs

代码语言:javascript
运行
复制
    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

代码语言:javascript
运行
复制
    [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

代码语言:javascript
运行
复制
@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" alt="Logo" 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

代码语言:javascript
运行
复制
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.BaseUri}authentication/login", stringContent);
                if (result.StatusCode == System.Net.HttpStatusCode.OK)
                {                       
                    NavMan.NavigateTo("/", true);
                }
                else
                {
                    ErrorMessage = await result.Content.ReadAsStringAsync();
                }                 
            }
        }
    }
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-10-20 16:00:48

我相信你需要把饼干附在回复上。我还没有用您的代码对此进行测试,但是它应该可以这样工作:

代码语言:javascript
运行
复制
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中存储的内容。我是这样做的:

代码语言:javascript
运行
复制
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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69619930

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档