GithubHelp home page GithubHelp logo

aspnetcoreidentity's Introduction

AspNetCoreIdentity

Реализация ASP.NET Core Identity и JWT на ASP.NET Core 3.1

Начальная конфигурация

Добавим конфигурацию для использования DataContext EntityFramework

services.AddDbContext<DataContext>(opt =>
	opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

И строку подключения к MSSQL базе в appsettings.json

"ConnectionStrings": {"DefaultConnection": "Server=.\\SQL_2016;Database=AspNetCoreIdentity;user id=testUser;password='testUser'"}

Добавим MediatR, чтобы реализовать в нашем приложение подход CQRS (о котором можно почитать здесь)

services.AddMediatR(typeof(LoginHandler).Assembly);

Добавляем ASP.NET Core Identity в проект

Первым делом добавим в Domain пользовательский класс AppUser.cs с необходимыми под наши задачи полями и унаследуем его от IdentityUser.

public class AppUser : IdentityUser                                                                                 
{
  public string DisplayName { get; set; }
}

Теперь зарегистрируем, созданный нами класс пользователя в Startup.cs

var builder = services.AddIdentityCore<AppUser>();
var identityBuilder = new IdentityBuilder(builder.UserType, builder.Services);

Добавим в проект EFData DataContext, унаследованный от IdentityDbContext

public class DataContext : IdentityDbContext<AppUser>
{
  public DataContext(DbContextOptions<DataContext> options) : base(options) { }
}

Установим тип хранилища (класс контекста данных) в Startup.cs, которое Identity будет использовать для хранения данных.

identityBuilder.AddEntityFrameworkStores<DataContext>();
identityBuilder.AddSignInManager<SignInManager<AppUser>>();

Подключим аутентификацию в методе Configure() класса Startup.cs

app.UseAuthentication();

На этом этапе мы уже можем создать инициальную миграцию, для этого стартовым проектом выбираем API, открываем Package Manager Console и в Default project выбираем EFData, запускаем создание инициальной миграции командой

> Add-migration initial

alt 1

Результатом будет автоматически созданная миграция в папке Migrations проекта EFData, прежде чем ее накатить на нашу базу, создадим DataSeed для автозаполнения начальными данными базы, в том же проекте, создаем класс DataSeed.

public class DataSeed
{
  public static async Task SeedDataAsync(DataContext context, UserManager<AppUser> userManager)
  {
    if (!userManager.Users.Any())
    {
      var users = new List<AppUser>
                      {
                        new AppUser
                            {
                              DisplayName = "TestUserFirst",
                              UserName = "TestUserFirst",
                              Email = "[email protected]"
                            },
                        new AppUser
                            {
                              DisplayName = "TestUserSecond",
                              UserName = "TestUserSecond",
                              Email = "[email protected]"
                             }
                         };

                foreach (var user in users)
                {
                    await userManager.CreateAsync(user, "qazwsX123@");
                }
    }
  }
}

Теперь можно запускать создание и обновление базы

> update-database

Результат alt 1

Все необходимое для работы с Identity добавленно, теперь реализуем метод для авторизации пользователя в нашем приложение.

В UserController проекта API добавим метод LoginAsync.

[HttpPost("login")]
public async Task<ActionResult<User>> LoginAsync(LoginQuery query)
{
  return await Mediator.Send(query);
}

В проект Application добавим три класса: LoginHandler.cs

public class LoginHandler : IRequestHandler<LoginQuery, User>
{
  private readonly UserManager<AppUser> _userManager;

  private readonly SignInManager<AppUser> _signInManager;

  public LoginHandler(UserManager<AppUser> userManager,SignInManager<AppUser> signInManager)
  {
    _userManager = userManager;
    _signInManager = signInManager;
  }

  public async Task<User> Handle(LoginQuery request, CancellationToken cancellationToken)
  {
    var user = await _userManager.FindByEmailAsync(request.Email);
    if (user == null)
    {
      throw new RestException(HttpStatusCode.Unauthorized);
    }

    var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);

    if (result.Succeeded)
    {
      return new User
                 {
                    DisplayName = user.DisplayName,
                    Token = "test", (Далее здесь будет вызов метода сервиса, генерирующий Token)                         
                    UserName = user.UserName,
                    Image = null
                  };
      }

      throw new RestException(HttpStatusCode.Unauthorized);
    }
}

LoginQuery.cs

public class LoginQuery : IRequest<User>
{
  public string Email { get; set; }
  public string Password { get; set; }
}

LoginQueryValidation.cs

public class LoginQueryValidation : AbstractValidator<LoginQuery>
{
  public LoginQueryValidation()
  {
    RuleFor(x => x.Email).NotEmpty();
    RuleFor(x => x.Password).NotEmpty();
  }
}

Протестируем метод при помощи любого API клиента (в нашем случае Postman), сделав запрос к веб-приложению (примеры моих запросов для Postman так же добавены на GitHub) Регистрация с результатом 401 Unauthorized

alt 7

Успешная регистрация с данными по пользователю в респонсе

alt 8

Добавим набор ограничений в Startup.cs, теперь каждый запрос к нашему API должен быть авторизован, единственный метод, который будет являться исключением (в дальнейшем их может быть больше) - это Login, для этого на UserController навесим атрибут [AllowAnonymous]

services.AddMvc(option => 
{
        option.EnableEndpointRouting = false;
        var policy = new AuthorizationPolicyBuilder()
                            .RequireAuthenticatedUser().RequireAuthenticatedUser().Build();
		option.Filters.Add(new AuthorizeFilter(policy));
	}).SetCompatibilityVersion(CompatibilityVersion.Latest);

Добавляем JWT в проект

Конфигурация взаимодейсвия нашего приложения с JWT производится достаточно просто. В проекте Application определим отдельный интерфейс IJwtGenerator с единственным методом CreateToken

public interface IJwtGenerator
{
  string CreateToken(AppUser user);
}

В проект Infrastructure добавим класс JwtGenerator и унаследуем его от IJwtGenerator, со следующей реализацией CreateToken

public class JwtGenerator : IJwtGenerator
{
  private readonly SymmetricSecurityKey _key;

  public JwtGenerator(IConfiguration config)
  {
    _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
  }

  public string CreateToken(AppUser user)
  {
    var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.NameId, user.UserName) };
            
    var credentials = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
      Subject = new ClaimsIdentity(claims),
      Expires = DateTime.Now.AddDays(7),
      SigningCredentials = credentials
    };
    
    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
  }
}

Зарегистрариуем реализацию в контейнере приложения

services.AddScoped<IJwtGenerator, JwtGenerator>();

Настроим проверку JWT

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("super secret key"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(
                opt =>
                     {
                        opt.TokenValidationParameters = new TokenValidationParameters
                                                            {
                                                              ValidateIssuerSigningKey = true,
                                                              IssuerSigningKey = key,
                                                              ValidateAudience = false,
                                                              ValidateIssuer = false,
                                                            };
                      });	

Защита данных

Сейчас значение строки указано в открытом виде и берется даже не из конфигов, для того, чтобы безопасно хранить секретные данные, предлагаю воспользоваться .NET user secret, подробнее об этом инструменте можно почитать здесь, я лишь добавлю что итоговый файл хранит незашифрованные данные, поэтому мы будем использовать его только во время разработке.

Для того чтобы включить секретное хранилище воспользуемся командой в PackageManager Console

> dotnet user-secrets init 

В API.csproj добавим UserSecretsId alt 9

Теперь в наше секретное хранилище можем поместить значение следующей командой

> dotnet user-secrets set "TokenKey" "super secret key" –p API/

alt 10

Для того чтобы просмотреть все что сейчас находится в хранилище, нужно воспользоваться командой

> dotnet user-secrets list –p API/

alt 11

После этого мы можешь использовать наши секретные данные следующим образом

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));

Теперь мы можем испольовать наш JwtGenerator в LoginHandler

return new User
{
  DisplayName = user.DisplayName,
  Token = _jwtGenerator.CreateToken(user),
  UserName = user.UserName,
  Image = null
};

Наш ответ при регистрации изменится и будет возвращать токен, с которым мы можем идти на сервер за данными

alt 12

Проверим работу веб-приложения, для того, чтобы запрос выполнился успешно в параметрах headers необходимо передать наш токен

alt 13

В случае, если токена не будет, вернется ошибка 401 Unauthorized

alt 14

Полезные ссылки

[The Onion Architecture] (https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/) [CQRS] (https://medium.com/@dbottiau/a-naive-introduction-to-cqrs-in-c-9d0d99cd2d54)

aspnetcoreidentity's People

Contributors

anastishka avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

aspnetcoreidentity's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.