sesiones arreglada

This commit is contained in:
2026-04-09 09:42:48 +02:00
parent df0e7c0c15
commit 174b9067c8
4 changed files with 222 additions and 33 deletions

View File

@@ -106,6 +106,7 @@
{
if (firstRender)
{
await LimpiarEstadoUsuarioAsync();
// Se crea una referencia a este componente para que JS pueda invocar SetToken
dotNetRef = DotNetObjectReference.Create(this);
await JSRuntime.InvokeVoidAsync("registerTokenReceiver", dotNetRef);
@@ -114,16 +115,21 @@
protected override void OnInitialized()
{
LimpiarEstadoUsuario();
LimpiarEstadoUsuarioLocal();
}
private void LimpiarEstadoUsuario()
private void LimpiarEstadoUsuarioLocal()
{
UserState.Token = "";
UserState.NombreUsu = "";
UserState.Clear();
HttpContextAccessor?.HttpContext?.Session?.Clear();
}
private async Task LimpiarEstadoUsuarioAsync()
{
LimpiarEstadoUsuarioLocal();
await JSRuntime.InvokeVoidAsync("registroPersonalAuth.clear");
}
public async Task LogIn()
{
mostrar = true;
@@ -168,6 +174,7 @@
UserState.Token = parsedJson["token"]?.ToString() ?? "";
// Actualizamos el nombre del usuario (formateado como "APELLIDOS, NOMBRE")
UserState.NombreUsu = $"{parsedJson["user"]?["apellidos"]?.ToString()}, {parsedJson["user"]?["nombre"]?.ToString()}";
await JSRuntime.InvokeVoidAsync("registroPersonalAuth.save", UserState.Token, UserState.NombreUsu);
Navigation.NavigateTo("/cumplimientoTrienios", true);
}
else
@@ -178,7 +185,7 @@
}
[JSInvokable]
public Task SetToken(string token, string userJson)
public async Task SetToken(string token, string userJson)
{
// Actualizamos el token en UserState
UserState.Token = token;
@@ -192,13 +199,54 @@
{
UserState.NombreUsu = "";
}
await JSRuntime.InvokeVoidAsync("registroPersonalAuth.save", UserState.Token, UserState.NombreUsu);
Navigation.NavigateTo("/cumplimientoTrienios", true);
return Task.CompletedTask;
}
}
<!-- Bloque de script inline para definir funciones de autenticación -->
<script>
window.registroPersonalAuth = {
save: async function(token, nombreUsu) {
const response = await fetch("/auth/state", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
credentials: "same-origin",
body: JSON.stringify({
token: token || "",
nombreUsu: nombreUsu || ""
})
});
if (!response.ok) {
throw new Error("No se pudo guardar el estado de autenticacion.");
}
},
clear: async function() {
await fetch("/auth/state", {
method: "DELETE",
credentials: "same-origin"
});
}
};
function buildNombreUsuario(user) {
if (!user) {
return "";
}
const apellidos = user.APELLIDOS || "";
const nombre = user.NOMBRE || "";
if (apellidos && nombre) {
return `${apellidos}, ${nombre}`;
}
return apellidos || nombre;
}
function registerTokenReceiver(dotnetRef) {
window.dotnetTokenReceiver = dotnetRef;
}
@@ -220,10 +268,14 @@
if (window.dotnetTokenReceiver) {
window.dotnetTokenReceiver.invokeMethodAsync("SetToken", data.token, JSON.stringify(data.user));
} else {
localStorage.setItem("token", data.token);
localStorage.setItem("user", JSON.stringify(data.user));
window.location.href = "/cumplimientoTrienios";
window.registroPersonalAuth
.save(data.token, buildNombreUsuario(data.user))
.then(function() {
window.location.href = "/cumplimientoTrienios";
})
.catch(function(error) {
console.error(error);
});
}
}
});

View File

@@ -1,13 +1,26 @@
namespace RegistroPersonalAN.Models
using Microsoft.AspNetCore.Http;
namespace RegistroPersonalAN.Models
{
public class UserState
{
private readonly object _lock = new object();
private string _token;
private string _NombreUsu;
private string _token = string.Empty;
private string _NombreUsu = string.Empty;
private bool _Mostrar;
private int _currentPage = 1;
private int _currentPage = 1;
public UserState(IHttpContextAccessor httpContextAccessor, UserStateCookieStore cookieStore)
{
var snapshot = cookieStore.Read(httpContextAccessor.HttpContext);
if (snapshot is null)
{
return;
}
_token = snapshot.Token;
_NombreUsu = snapshot.NombreUsu ?? string.Empty;
}
public string Token
{
@@ -22,10 +35,11 @@
{
lock (_lock)
{
_token = value;
_token = value ?? string.Empty;
}
}
}
public string NombreUsu
{
get
@@ -39,10 +53,11 @@
{
lock (_lock)
{
_NombreUsu = value;
_NombreUsu = value ?? string.Empty;
}
}
}
public bool Mostrar
{
get
@@ -63,8 +78,42 @@
public int CurrentPage
{
get { lock (_lock) { return _currentPage; } }
set { lock (_lock) { _currentPage = value; } }
get
{
lock (_lock)
{
return _currentPage;
}
}
set
{
lock (_lock)
{
_currentPage = value;
}
}
}
public bool IsAuthenticated
{
get
{
lock (_lock)
{
return !string.IsNullOrWhiteSpace(_token);
}
}
}
public void Clear()
{
lock (_lock)
{
_token = string.Empty;
_NombreUsu = string.Empty;
_Mostrar = false;
_currentPage = 1;
}
}
}
}

View File

@@ -0,0 +1,83 @@
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.WebUtilities;
namespace RegistroPersonalAN.Models
{
public sealed class UserStateCookieStore
{
private const string CookieName = "RegistroPersonalAN.AuthState";
private readonly IDataProtector _protector;
public UserStateCookieStore(IDataProtectionProvider dataProtectionProvider)
{
_protector = dataProtectionProvider.CreateProtector("RegistroPersonalAN.UserStateCookieStore.v1");
}
public UserStateCookiePayload? Read(HttpContext? httpContext)
{
if (httpContext?.Request.Cookies.TryGetValue(CookieName, out var cookieValue) != true ||
string.IsNullOrWhiteSpace(cookieValue))
{
return null;
}
try
{
var protectedPayload = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(cookieValue));
var json = _protector.Unprotect(protectedPayload);
var payload = JsonSerializer.Deserialize<UserStateCookiePayload>(json);
if (payload is null || string.IsNullOrWhiteSpace(payload.Token))
{
return null;
}
return payload;
}
catch
{
return null;
}
}
public void Write(HttpContext httpContext, UserStateCookiePayload payload)
{
if (string.IsNullOrWhiteSpace(payload.Token))
{
Clear(httpContext);
return;
}
var json = JsonSerializer.Serialize(payload);
var protectedPayload = _protector.Protect(json);
var cookieValue = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(protectedPayload));
httpContext.Response.Cookies.Append(CookieName, cookieValue, BuildCookieOptions(httpContext));
}
public void Clear(HttpContext httpContext)
{
httpContext.Response.Cookies.Delete(CookieName, BuildCookieOptions(httpContext));
}
private static CookieOptions BuildCookieOptions(HttpContext httpContext)
{
return new CookieOptions
{
HttpOnly = true,
IsEssential = true,
Path = "/",
SameSite = SameSiteMode.Lax,
Secure = httpContext.Request.IsHttps
};
}
}
public sealed class UserStateCookiePayload
{
public string Token { get; set; } = string.Empty;
public string NombreUsu { get; set; } = string.Empty;
}
}

View File

@@ -1,12 +1,10 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Cookies;
using RegistroPersonalAN.Components;
using RegistroPersonalAN.Models;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
// Configurar servicios
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
@@ -24,7 +22,7 @@ builder.Services.AddAuthentication(options =>
options.LoginPath = "/home";
options.AccessDeniedPath = "/AccessDenied";
});
// Necesario para ver porqu<EFBFBD> est<EFBFBD> fallando ciertas cosas que dan el error Circuit
// Necesario para ver porqué está fallando ciertas cosas que dan el error Circuit
builder.Services.AddServerSideBlazor().AddCircuitOptions(option => { option.DetailedErrors = true; });
builder.Services.AddHttpContextAccessor();
builder.Services.AddDistributedMemoryCache();
@@ -39,17 +37,14 @@ builder.Services.AddHttpClient("CertClient").ConfigurePrimaryHttpMessageHandler(
{
return new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual // Forzar la selecci<EFBFBD>n del certificado
ClientCertificateOptions = ClientCertificateOption.Manual // Forzar la selección del certificado
};
});
builder.Services.AddBlazorBootstrap();
builder.Services.AddAntiforgery();
builder.Services.AddSingleton<UserState>();
builder.Services.AddSingleton<UserStateCookieStore>();
builder.Services.AddScoped<UserState>();
var app = builder.Build();
@@ -67,9 +62,20 @@ app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapPost("/auth/state", (HttpContext context, UserStateCookiePayload payload, UserStateCookieStore cookieStore) =>
{
cookieStore.Write(context, payload);
return Results.Ok();
}).DisableAntiforgery();
app.MapDelete("/auth/state", (HttpContext context, UserStateCookieStore cookieStore) =>
{
cookieStore.Clear(context);
return Results.NoContent();
}).DisableAntiforgery();
app.Use(async (context, next) =>
{
var userState = context.RequestServices.GetService<UserState>();
@@ -77,6 +83,7 @@ app.Use(async (context, next) =>
// Permitir solicitudes internas y recursos necesarios
if (path == "/" ||
path.StartsWithSegments("/auth/state") ||
path.StartsWithSegments("/_blazor") ||
path.StartsWithSegments("/Content") ||
path.StartsWithSegments("/Scripts") ||
@@ -88,8 +95,8 @@ app.Use(async (context, next) =>
return;
}
// Redirigir al home si no hay token y la ruta no es p<EFBFBD>blica
if (userState?.Token == null)
// Redirigir al home si no hay token y la ruta no es pública
if (string.IsNullOrWhiteSpace(userState?.Token))
{
Console.WriteLine($"Redirigiendo al home desde: {path}");
context.Response.Redirect("/");
@@ -100,8 +107,6 @@ app.Use(async (context, next) =>
await next();
});
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();