Авторизация по ролям

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 13.12.2019

Авторизация по ролям позволяет разграничить доступ к ресурсам в зависимости от группы, к которой принадлежит пользователь. ASP.NET Core позволяет легко создать разграничение доступа по ролям.

Для работы с ролями создадим новый проект по типу ASP.NET Core Web App (Model-View-Controller), который назовем RolesApp:

Работа с ролями в ASP.NET Core

После создания проекта добавим в него в папку Models классы, которые будут представлять пользователя и его роль:

public class User
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }

    public int? RoleId { get; set; }
    public Role Role { get; set; }
}
public class Role
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<User> Users { get; set; }
    public Role()
    {
        Users = new List<User>();
    }
}

Данные классы связаны отношением один-ко-многим, то есть один пользователь может иметь только одну роль, а к одной роли могут принадлежать несколько пользователей.

Для взаимодействия с MS SQL Server через Entity Framework добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. А затем добавим в папку Models новый класс ApplicationContext, который будет представлять контекст данных:

using Microsoft.EntityFrameworkCore;

namespace RolesApp.Models
{
    public class ApplicationContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Role> Roles { get; set; }
        public ApplicationContext(DbContextOptions<ApplicationContext> options)
            : base(options)
        {
            Database.EnsureCreated();
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            string adminRoleName = "admin";
            string userRoleName = "user";

            string adminEmail = "admin@mail.ru";
            string adminPassword = "123456";

            // добавляем роли
            Role adminRole = new Role { Id = 1, Name = adminRoleName };
            Role userRole = new Role { Id = 2, Name = userRoleName };
            User adminUser = new User { Id = 1, Email = adminEmail, Password = adminPassword, RoleId = adminRole.Id };

            modelBuilder.Entity<Role>().HasData(new Role[] { adminRole, userRole });
            modelBuilder.Entity<User>().HasData( new User[] { adminUser });
            base.OnModelCreating(modelBuilder);
        }
    }
}

Для инициализации базы данных в методе OnModelCreating() добавляются в бд две роли и один пользователь - администратора.

Теперь изменим класс Startup для установки и использования контекста данных:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.Cookies;

using RolesApp.Models;

namespace RolesApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            string connection = "Server=(localdb)\\mssqllocaldb;Database=rolesappdb;Trusted_Connection=True;";
            services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Login");
                    options.AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Account/Login");
                });

            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseDeveloperExceptionPage();

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Далее добавим в папку Controllers новый контроллер AccountController, который будет выполнять регистрацию и логин пользователей.

Для регистрации определим в папке Models вспомогательные модели RegisterModel и LoginModel:

using System.ComponentModel.DataAnnotations;

namespace RolesApp.Models
{
    public class RegisterModel
    {
        [Required(ErrorMessage = "Не указан Email")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Не указан пароль")]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Compare("Password", ErrorMessage = "Пароль введен неверно")]
        public string ConfirmPassword { get; set; }
    }
	
	public class LoginModel
    {
        [Required(ErrorMessage = "Не указан Email")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Не указан пароль")]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}

И также для представлений контроллера добавим в папку Views подкаталог Account и поместим в него новое представление Register.cshtml:

@model RolesApp.Models.RegisterModel

<h2>Регистрация</h2>

<form asp-action="Register" asp-controller="Account" asp-anti-forgery="true">
    <div class="validation" asp-validation-summary="ModelOnly"></div>
    <div>
        <div>
            <label asp-for="Email">Введите Email</label><br />
            <input type="text" asp-for="Email" />
            <span asp-validation-for="Email" />
        </div>
        <div>
            <label asp-for="Password">Введите пароль</label><br />
            <input asp-for="Password" />
            <span asp-validation-for="Password" />
        </div>
        <div>
            <label asp-for="ConfirmPassword">Повторите пароль</label><br />
            <input asp-for="ConfirmPassword" />
            <span asp-validation-for="ConfirmPassword" />
        </div>
        <div>
            <input type="submit" value="Регистрация" />
        </div>
    </div>
</form>

А также добавим представление Login.cshtml для логина пользователей:

@model RolesApp.Models.LoginModel

<h2>Вход на сайт</h2>

<a asp-action="Register" asp-controller="Account">Регистрация</a>

<form asp-action="Login" asp-controller="Account" asp-anti-forgery="true">
    <div class="validation" asp-validation-summary="ModelOnly"></div>
    <div>
        <div class="form-group">
            <label asp-for="Email">Введите Email</label>
            <input type="text" asp-for="Email" />
            <span asp-validation-for="Email" />
        </div>
        <div class="form-group">
            <label asp-for="Password">Введите пароль</label>
            <input asp-for="Password" />
            <span asp-validation-for="Password" />
        </div>
        <div class="form-group">
            <input type="submit" value="Войти" class="btn" />
        </div>
    </div>
</form>

А в контроллере AccountController определим методы для логина и регистрации:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using RolesApp.Models;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;

namespace RolesApp.Controllers
{
    public class AccountController : Controller
    {
        private ApplicationContext _context;
        public AccountController(ApplicationContext context)
        {
            _context = context;
        }
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                User user = await _context.Users.FirstOrDefaultAsync(u => u.Email == model.Email);
                if (user == null)
                {
                    // добавляем пользователя в бд
                    user = new User { Email = model.Email, Password = model.Password };
                    Role userRole = await _context.Roles.FirstOrDefaultAsync(r => r.Name == "user");
                    if (userRole != null)
                        user.Role = userRole;

                    _context.Users.Add(user);
                    await _context.SaveChangesAsync();

                    await Authenticate(user); // аутентификация

                    return RedirectToAction("Index", "Home");
                }
                else
                    ModelState.AddModelError("", "Некорректные логин и(или) пароль");
            }
            return View(model);
        }
        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginModel model)
        {
            if (ModelState.IsValid)
            {
                User user = await _context.Users
                    .Include(u => u.Role)
                    .FirstOrDefaultAsync(u => u.Email == model.Email && u.Password == model.Password);
                if (user != null)
                {
                    await Authenticate(user); // аутентификация

                    return RedirectToAction("Index", "Home");
                }
                ModelState.AddModelError("", "Некорректные логин и(или) пароль");
            }
            return View(model);
        }
        private async Task Authenticate(User user)
        {
            // создаем один claim
            var claims = new List<Claim>
            {
                new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email),
                new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role?.Name)
            };
            // создаем объект ClaimsIdentity
            ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType,
                ClaimsIdentity.DefaultRoleClaimType);
            // установка аутентификационных куки
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id));
        }
    }
}

При регистрации пользователю будет присваиваться роль "user", которая, как ожидается, добавляется в базу данных с помощью инициализации в классе Startup.

Ключевым моментом здесь является установка claim в методе Authenticate(). Кроме claim, который хранит логин пользователя, здесь также устанавливается и его роль:

var claims = new List<Claim>
{
    new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email),
    new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role?.Name)
};

Для указания роли здесь применяется тип claim ClaimsIdentity.DefaultRoleClaimType, а в качестве значения для этого типа используется имя роли. По сути больше для установки роли для пользователя ничего не нужно.

В итоге у нас получится следующий проект:

Авторизация по ролям в ASP.NET Core

И в конце мы сможем использовать ограничение по ролям. Например:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;

namespace RolesApp.Controllers
{
    public class HomeController : Controller
    {
        [Authorize(Roles = "admin, user")]
        public IActionResult Index()
        {
            string role = User.FindFirst(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Value;
            return Content($"ваша роль: {role}");
        }
        [Authorize(Roles = "admin")]
        public IActionResult About()
        {
            return Content("Вход только для администратора");
        }
    }
}

Здесь доступ к методу Index имеют представлители ролей "admin" и "user". А доступ к методу About - только администраторы.

Используя тип claimа ClaimsIdentity.DefaultRoleClaimType, в методе Index мы можем получить его значение - то есть роль пользователя.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850