Как реализовать управление доступом на основе ролей в веб-API .NET?C#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Как реализовать управление доступом на основе ролей в веб-API .NET?

Сообщение Anonymous »

В настоящее время я работаю над улучшением своих навыков работы с веб-API.
Я пытался реализовать их с помощью класса IdentityRole, а также класса Identity User, но, похоже, у меня ничего не получилось. подожди. Честно говоря, я даже не уверен, что я делаю не так.
Я успешно инициировал и заполнил обе таблицы через DbContext Seeder, успешно вошел в систему, зарегистрировал пользователя и правильно ввел значение по умолчанию. для роли:
Это обе мои конечные точки 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 succesfully");

}
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;
}
}
Токен или JWT скорее создается и отправляется обратно клиенту правильно.
Однако, когда я пытаюсь вызвать его с помощью [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");
}

}
Теперь я понимаю, что, возможно, нужно сделать что-то еще, чего я не делаю. И, вероятно, есть что-то еще, что нужно сделать или настроить в моем program.cs. Но что? Что еще мне нужно для правильной обработки защищенных маршрутов в моем API?
Это вся моя программа.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();
Я пытался переделать программу несколько раз, но не могу найти проблему, все работает правильно, это просто атрибут или проверка JWT, которая, похоже, не работает. работает.
В случае необходимости я также поделюсь своим DbContext и сущностью Usuario, которая представляет собой просто прямой IdentityUser, который я связал отношением один к одному с сущностью Persona, которая содержит вся личная информация.
Это мой 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));
}

}
Моя модель Usuario:

Код: Выделить всё

   public class Usuario : IdentityUser
{
public int? PersonaId { get; set; }

public Persona? Persona { get; set; } = null!;
}
Как видите, это простой DbContext. Идея этого проекта заключалась в том, чтобы создать для меня шаблон, который будет служить руководством по реализации «полного» контроля доступа на основе ролей с использованием инструментов, предоставляемых EntityFrameWork, таких как IdentityRole и IdentityUser.
Я понимаю, что мог бы просто обновить свою модель, чтобы она также обрабатывала свойство роли и проверяла его в соответствующих конечных точках, но я не считаю, что это наиболее понятный и визуально привлекательный способ сделать это. Итак.
Любые рекомендации, советы или ресурсы по решению этой конкретной проблемы или любой другой, которые могут присутствовать в моем простом API, будут очень признательны.
Спасибо, что уделили время.


Подробнее здесь: https://stackoverflow.com/questions/792 ... et-web-api
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «C#»