Я пытался реализовать их с помощью класса IdentityRole, а также класса Identity User, но у меня не получилось. кажется, разобрался. Честно говоря, я не уверен, что я вообще делаю неправильно.
Я успешно инициировал и заполнил обе таблицы через сеялку DbContext, успешно вошел в систему и зарегистрировал пользователя и правильно введите значение по умолчанию для роли.
Это обе мои конечные точки Usuario, которые обрабатывают как вход, так и регистрацию:
Код: Выделить всё
public async Task Register(NuevoUsuarioDto nuevoUsuarioDto)
{
try
{
if (!ModelState.IsValid)
return BadRequest("Invalid Input Data");
var newPersona = await _personaRepo.CreateAsync(nuevoUsuarioDto.ToPersonaFromNuevoUsuarioDto());
if (newPersona == null)
return StatusCode(500, "Internal Server Error 500");
var newUsuario = new Usuario
{
UserName = nuevoUsuarioDto.Username,
Email = nuevoUsuarioDto.Email,
PersonaId = newPersona.Id
};
var usuarioCreado = await _userManager.CreateAsync(newUsuario, nuevoUsuarioDto.Password);
if (!usuarioCreado.Succeeded)
return StatusCode(500, "Internal Server Error 500");
var roleResult = await _userManager.AddToRoleAsync(newUsuario, "Admin");
if (!roleResult.Succeeded)
return StatusCode(500, "Internal Server Error 500");
return Ok("User Registered successfully");
}
catch (System.Exception)
{
return StatusCode(500, "Internal Server Error 500");
}
}
Код: Выделить всё
[Route("login")]
[HttpPost]
public async Task login(LoginDto loginDto)
{
try
{
if (!ModelState.IsValid)
return BadRequest("Invalid Input Data");
var usuario = await _userManager.Users.FirstOrDefaultAsync(u => u.UserName == loginDto.Username);
if (usuario == null)
return Unauthorized("Invalid Username or Password");
var checkPassword = await _signInManager.CheckPasswordSignInAsync(usuario, loginDto.Password, false);
if (!checkPassword.Succeeded)
return Unauthorized("Invalid Username or Password");
var token = await _tokenService.CreateToken(usuario);
Response.Cookies.Append("auth_token", token, new CookieOptions
{
HttpOnly = true,
Secure = false, // Use true in production with HTTPS
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddHours(1)
});
return Ok(new { Message = "Login successful" });
}
catch (System.Exception)
{
return StatusCode(500, "Internal Server Error 500");
}
}
Я использовал JWT для обработки и безопасного хранения ролей пользователя следующим образом:
Код: Выделить всё
public async Task CreateToken(Usuario usuario)
{
if (string.IsNullOrEmpty(usuario.UserName))
throw new ArgumentException("UserName is required.");
if (string.IsNullOrEmpty(usuario.Email))
throw new ArgumentException("Email is required.");
var claims = new List
{
new Claim(JwtRegisteredClaimNames.Email, usuario.Email),
new Claim(JwtRegisteredClaimNames.GivenName, usuario.UserName),
};
var userRoles = await _userManager.GetRolesAsync(usuario) ?? new List();
foreach (var role in userRoles)
{
if (!string.IsNullOrEmpty(role))
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
}
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = creds,
Issuer = _config["JWT:Issuer"],
Audience = _config["JWT:Audience"]
};
try
{
var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
throw;
}
}
Тем не менее, когда я пытаюсь вызвать его с помощью [ Authorize(Roles="")], он вообще не работает.
Код: Выделить всё
[ApiController]
[Route("/api/test/")]
public class TestController : ControllerBase
{
[HttpGet]
public async Task Test()
{
return Ok("Reached");
}
[HttpGet]
[Route("client")]
[Authorize(Roles = "Cliente")]
public async Task TestClient()
{
return Ok("This was a client");
}
[HttpGet("admin")]
[Authorize(Roles = "Admin")]
public async Task TestAdmin()
{
return Ok("This was an Admin");
}
}
Это вся программа.cs:
Код: Выделить всё
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
//For relationships
builder.Services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
// Register DbContext with PostgreSQL
builder.Services.AddDbContext(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
// Authentication / authorization and JWT config
// In order to create and add to the program the identities
builder.Services.AddIdentity(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequiredLength = 12;
}).AddEntityFrameworkStores();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme =
options.DefaultChallengeScheme =
options.DefaultForbidScheme =
options.DefaultScheme =
options.DefaultSignInScheme =
options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JWT:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["JWT:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
System.Text.Encoding.UTF8.GetBytes(builder.Configuration["JWT:SigningKey"] ?? "")
),
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
В случае необходимости я также поделюсь своим DbContext и объектом Usuario, который представляет собой просто IdentityUser, который я связал отношения один к одному с Персона, содержащая всю личную информацию.
Это мой DbContext:
Код: Выделить всё
public class AppDbContext : IdentityDbContext
{
public AppDbContext(DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity
().HasData(
new Persona
{
Id = 1,
Nombre = "Ismael",
ApellidoPaterno = "Moron",
ApellidoMaterno = "Pedraza",
Carnet = "12597382",
FechaNacimiento = DateOnly.Parse("1997-10-28"),
UsuarioId = "1"
}
);
var hasher = new PasswordHasher();
builder.Entity().HasData(
new Usuario
{
Id = "1",
UserName = "ismael",
Email = "[email protected]",
PasswordHash = hasher.HashPassword(null, "123456"),
PersonaId = 1
}
);
List roles = new List
{
new IdentityRole
{
Name="Admin",
NormalizedName = "ADMIN"
},
new IdentityRole
{
Name="Cliente",
NormalizedName = "Cliente"
}
};
builder.Entity().HasData(roles);
base.OnModelCreating(builder);
// Configure one-to-one relationship between Usuario and Persona
builder.Entity()
.HasOne(u => u.Persona)
.WithOne(p => p.Usuario)
.HasForeignKey(u => u.PersonaId)
.OnDelete(DeleteBehavior.Cascade); // Optional: cascade delete if needed
}
public DbSet Personas { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ConfigureWarnings(warnings =>
warnings.Ignore(RelationalEventId.PendingModelChangesWarning));
}
}
Код: Выделить всё
public class Usuario : IdentityUser
{
public int? PersonaId { get; set; }
public Persona? Persona { get; set; } = null!;
}
Я понимаю, что мог бы просто обновить свою модель, чтобы она также обрабатывала свойство роли и проверяла его в соответствующих конечных точках, но я не думаю, что это наиболее понятно. и визуально привлекательный способ сделать Итак.
Любые рекомендации, советы или ресурсы по решению этой конкретной проблемы или любой другой, которые могут присутствовать в моем простом API, будут очень признательны.
Спасибо, что уделили время.
Подробнее здесь: https://stackoverflow.com/questions/792 ... re-web-api