GithubHelp home page GithubHelp logo

ihouger / aspnetcoreidentity Goto Github PK

View Code? Open in Web Editor NEW

This project forked from abushmeleva/aspnetcoreidentity

0.0 0.0 0.0 4.22 MB

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

C# 98.48% Dockerfile 1.52%

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)

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.