JWT аутентификация

В связи с ростом количества API-интерфейсов и их глобального потребления в наши дни, безопасность API чрезвычайно важна. Аутентификация JWT — это стандартный способ защиты API-интерфейсов. Он позволяет проверять данные, передаваемые по сети между API-интерфейсами и клиентами, которые используют API-интерфейсы.

Веб-токены JSON (широко известные как JWT) — это открытый стандарт для передачи данных между клиентом и сервером, позволяющий безопасно передавать данные между сервером и потребителями. В этой статье рассказывается о том, как можно использовать JWT для защиты API.

Что понадобится

Если вы собираетесь работать с примерами кода, обсуждаемыми в этой статье, в вашей системе должно быть установлено следующее:

  • Visual Studio 2019 (более ранняя версия также будет работать, но предпочтительнее Visual Studio 2019)
  • .NET 5.0
  • Среда выполнения ASP.NET 5.0

Вы можете скачать Visual Studio 2019 отсюда: https://visualstudio.microsoft.com/downloads/. Вы можете загрузить среду выполнения .NET 5.0 и ASP.NET 5.0 отсюда: https://dotnet.microsoft.com/download/dotnet/5.0.

Что такое веб-токены JSON (JWT)?

JSON Web Token — это открытый стандарт (RFC 7519), который определяет безопасный, компактный и автономный защищенный способ передачи информации между отправителем и получателем через URL-адрес, параметр POST, или внутри заголовка HTTP. Следует отметить, что информация, которая должна безопасно передаваться между двумя сторонами, представлена в формате JSON и криптографически подписана для проверки ее подлинности. JWT обычно используется для реализации аутентификации и авторизации в веб-приложениях. Поскольку JWT является стандартом, все JWT являются токенами, но не наоборот. Вы можете работать с веб-токенами JSON в .NET, Python, Node.js, Java, PHP, Ruby, Go, JavaScript и т. д..

На рисунке 1 показано, как работает типичная аутентификация JWT.

Рисунок 1: Аутентификация JWT в действии

Рисунок 1: Аутентификация JWT в действии

JWT представлен как комбинация трех частей в кодировке base64url, объединенных символами точки (‘.’), И состоит из следующих трех разделов:

  • Заголовок
  • Полезная нагрузка
  • Подпись

Раздел заголовка (Header)

В этом разделе представлены метаданные о типе данных и алгоритме, который будет использоваться для шифрования данных, которые должны быть переданы. Заголовок JWT состоит из трех разделов — в их число входят: метаданные для токена, тип подписи и алгоритм шифрования. Он состоит из двух свойств, то есть «alg» и «typ». Хотя первый относится к используемому алгоритму криптографии, то есть к HS256 в данном случае, последний используется для указания типа токена, то есть JWT.

[pastacode lang=»javascript» manual=»%7B%0A%20%20%22typ%22%3A%20%22JWT%22%2C%0A%20%20%22alg%22%3A%20%22HS256%22%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Полезная нагрузка (payload)

Полезная нагрузка представляет собой фактическую информацию в формате JSON, которая должна передаваться по сети. Приведенный ниже фрагмент кода иллюстрирует простую полезную нагрузку.

[pastacode lang=»javascript» manual=»%7B%0A%20%20%22sub%22%3A%20%221234567890%22%2C%0A%20%20%22name%22%3A%20%22Joydip%20Kanjilal%22%2C%0A%20%20%22admin%22%3A%20true%2C%0A%20%20%22jti%22%3A%20%22cdafc246-109d-4ac9-9aa1-eb689fad9357%22%2C%0A%20%20%22iat%22%3A%201611497332%2C%0A%20%20%22exp%22%3A%201611500932%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Payload обычно содержат утверждения (claims), идентификационную информацию пользователя,  разрешения и т. д. Вы можете использовать утверждения для передачи дополнительной информации. Они также называются утверждениями JWT (JWT claims) и бывают двух типов: зарезервированные и настраиваемые. Вот список зарезервированных требований:

  • iss: представляет эмитента токена.
  • sub: Это предмет токена.
  • aud: представляет аудиторию токена.
  • exp: используется для определения срока действия токена.
  • nbf: используется для указания времени, до которого токен не должен обрабатываться.
  • iat: это время, когда токен был выпущен.
  • jti: представляет собой уникальный идентификатор токена.

Вы также можете использовать настраиваемые утверждения, которые можно добавить к токену с помощью правила.

Подпись (Signature)

Подпись соответствует спецификации JSON Web Signature (JWS) и используется для проверки целостности данных  передаваемых по сети. Этот раздел содержит хэш заголовка, полезной нагрузки и секретного ключа, и используется для гарантии того, что сообщение не было изменено во время передачи. Окончательно подписанный токен создается в соответствии со спецификацией JSON Web Signature (JWS). Закодированный заголовок JWT, а также закодированная полезная нагрузка JWT объединяются, а затем подписываются с использованием надежного алгоритма шифрования, такого как HMAC SHA 256.

Приступим к реализации.

Сначала создайте новый проект ASP.NET Core MVC 5 в Visual Studio 2019. Создать проект в Visual Studio 2019 можно несколькими способами. Когда вы запустите Visual Studio 2019, вы увидите стартовое окно. Вы можете выбрать «Продолжить без кода», чтобы открыть главный экран IDE Visual Studio 2019. В меню главного экрана вы можете выбрать File> New> Project, чтобы открыть экран, показанный на рисунке 2 и 3.

Создание приложения

Рисунок 2: Выбор типа создаваемого проекта

Настройка среды выполнения

Рисунок 3: настройка проекта

Затем выполните последовательность шагов в Visual Studio 2019, чтобы создать новый проект ASP.NET Core MVC 5.

Устанавливаем необходимые  NuGet пакеты

  Следующим шагом является установка необходимых пакетов NuGet. Пакет NuGet представлен как файл с расширением .nupkg и состоит из скомпилированного кода ( DLL), других связанных файлов и манифеста, который предоставляет информацию, связанную с пакетом, такую как номер версии и т. д. Чтобы установить необходимые пакеты в свой проект, выполните следующие команды в консоли диспетчера пакетов NuGet:

  • dotnet add package Microsoft.AspNetCore.Authentication
  • dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Реализация JWT в ASP.NET Core 5 MVC

В этом разделе я рассмотрю, как можно реализовать аутентификацию JWT в приложении ASP.NET Core MVC 5. В этом примере вы будете использовать следующие классы и интерфейсы:

  • HomeController: Это класс контроллера, который содержит все методы действий.
  • ITokenService: Это интерфейс, который содержит объявление двух методов, то есть BuildToken и IsTokenValid. Первый используется для создания токена, а второй — для проверки действительности данного токена.
  • TokenService: Этот класс расширяет интерфейс ITokenService и реализует его методы.
  • IUserRepository: Этот интерфейс содержит объявление метода GetUser, который используется для получения экземпляра UserDTO на основе имени пользователя из экземпляра класса UserModel.
  • UserRepository: Класс UserRepository расширяет интерфейс IUserRepository и реализует метод GetUser. Он также содержит образцы данных, используемых приложением в виде списка класса UserDTO.

Создание классов модели

В этом приложении можно использовать две сущности: UserModel и классы UserDTO. Следующий фрагмент кода показывает, как выглядит класс UserModel:

[pastacode lang=»c» manual=»public%20class%20UserModel%0A%7B%0A%20%20%20%20%5BRequired%5D%0A%20%20%20%20public%20string%20UserName%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20%0A%20%20%20%20%5BRequired%5D%0A%20%20%20%20public%20string%20Password%20%7B%20get%3B%20set%3B%20%7D%0A%7D» message=»» highlight=»» provider=»manual»/]

Затем создайте класс с именем UserDTO со следующим содержимым:

[pastacode lang=»c» manual=»public%20class%20UserDTO%0A%7B%0A%20%20%20%20public%20string%20UserName%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20public%20string%20Password%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20public%20string%20Role%20%7B%20get%3B%20set%3B%20%7D%0A%7D» message=»» highlight=»» provider=»manual»/]

UserDTO представляет объект передачи пользовательских данных и содержит три строковых свойства: UserName, Password и Role. Вы будете использовать этот класс в нескольких местах вашего приложения.

Настройка JWT в файле AppSettings

Создайте раздел в файле appsettings.cs с именем Jwt со следующим содержимым :

[pastacode lang=»javascript» manual=»%22Jwt%22%3A%20%7B%0A%20%20%20%20%22Key%22%3A%20%22%D0%97%D0%B4%D0%B5%D1%81%D1%8C%20%D0%B2%D1%8B%20%D0%B4%D0%BE%D0%BB%D0%B6%D0%BD%D1%8B%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D1%8C%20%D1%81%D0%B2%D0%BE%D0%B9%20%D1%81%D0%B5%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D1%8B%D0%B9%20%D0%BA%D0%BB%D1%8E%D1%87%2C%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B9%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D0%B5%D1%82%D1%81%D1%8F%20%D0%B4%D0%BB%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D0%B8%20%D0%B8%20%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B8%20%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%BE%D0%B2%20Jwt.%22%2C%0A%20%20%20%20%22Issuer%22%3A%20%22zznob.ru%22%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Замените текст, упомянутый в пункте «Key» выше, фактическим ключом, который вы хотите использовать в качестве секрета. После добавления нового раздела ваш файл appsettings.json будет выглядеть так:

[pastacode lang=»javascript» manual=»%7B%0A%20%20%20%20%22Logging%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22LogLevel%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Default%22%3A%20%22Information%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Microsoft%22%3A%20%22Warning%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Microsoft.Hosting.Lifetime%22%3A%20%22Information%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22Jwt%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Key%22%3A%20%22%D0%A1%D1%83%D0%BF%D0%B5%D1%80%D1%81%D0%B5%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D1%8B%D0%B9%20%D0%BA%D0%BB%D1%8E%D1%87%22%2C%0A%20%20%20%20%20%20%20%20%22Issuer%22%3A%20zznob.ru%0A%20%20%20%20%20%20%20%20%22Audience%22%3A%20%22http%3A%2F%2Flocalhost%3A5000%2F%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22AllowedHosts%22%3A%20%22*%22%0A%7D» message=»» highlight=»» provider=»manual»/]

Настраиваем аутентификацию с использованием Bearer и JWT

В методе ConfigureServices класса Startup я должен упомянуть, что буду использовать функцию AddAuthentication, а также JwtBearer, используя метод AddJwtBearer, как показано во фрагменте кода ниже.

[pastacode lang=»c» manual=»services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options%20%3D%3E%0A%7B%0A%20%20%20%20options.TokenValidationParameters%20%3D%20new%20TokenValidationParameters%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20ValidateIssuer%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20ValidateAudience%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20ValidateLifetime%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20ValidateIssuerSigningKey%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20ValidIssuer%20%3D%20Configuration%5B%22Jwt%3AIssuer%22%5D%2C%0A%20%20%20%20%20%20%20%20ValidAudience%20%3D%20Configuration%5B%22Jwt%3AIssuer%22%5D%2C%0A%20%20%20%20%20%20%20%20IssuerSigningKey%20%3D%20new%0A%20%20%20%20%20%20%20%20SymmetricSecurityKey%0A%20%20%20%20%20%20%20%20(Encoding.UTF8.GetBytes%0A%20%20%20%20%20%20%20%20(Configuration%5B%22Jwt%3AKey%22%5D))%0A%20%20%20%20%7D%3B%0A%7D)%3B%0A» message=»» highlight=»» provider=»manual»/]

Следующий код можно использовать в методе ConfigureServices класса Startup для добавления временной службы типа IUserRepository и IITokenService соответственно.

[pastacode lang=»c» manual=»services.AddTransient%3CIUserRepository%2C%20UserRepository%3E()%3B%0Aservices.AddTransient%3CITokenService%2C%20TokenService%3E()%3B%0A» message=»» highlight=»» provider=»manual»/]

После добавления этих экземпляров вы можете воспользоваться внедрением зависимостей в конструкторе класса HomeController для извлечения этих экземпляров из контейнера.

Полный код метода ConfigureServices приведен в листинге 1.

[pastacode lang=»c» manual=»public%20void%20ConfigureServices(IServiceCollection%20services)%0A%7B%0A%20%20%20%20services.AddControllers()%3B%0A%20%20%20%20services.AddTransient%3CIUserRepository%2C%20UserRepository%3E()%3B%0A%20%20%20%20services.AddTransient%3CITokenService%2C%20TokenService%3E()%3B%0A%20%20%20%20services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options%20%3D%3E%0A%20%20%20%20%7B%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20options.TokenValidationParameters%20%3D%20new%20TokenValidationParameters%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidateIssuer%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidateAudience%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidateLifetime%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidateIssuerSigningKey%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidIssuer%20%3D%20Configuration%5B%22Jwt%3AIssuer%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidAudience%20%3D%20Configuration%5B%22Jwt%3AIssuer%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20IssuerSigningKey%20%3D%20new%20SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration%5B%22Jwt%3AKey%22%5D))%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D)%3B%0A%7D%0A» message=»Листинг1: Метод ConfigureServices» highlight=»» provider=»manual»/]

В этом примере вы воспользуетесь состоянием сеанса для хранения сгенерированного токена. Вы должны вызвать метод расширения UseSession в методе Configure класса Startup, чтобы включить состояние сеанса для вашего приложения. Приведенный ниже фрагмент кода показывает, как вы можете получить сгенерированный токен из сеанса, а затем добавить его в качестве токена-носителя в заголовок запроса.

[pastacode lang=»c» manual=»app.Use(async%20(context%2C%20next)%20%3D%3E%0A%7B%0A%20%20%20%20var%20token%20%3D%20context.Session.GetString(%22Token%22)%3B%0A%20%20%20%20if%20(!string.IsNullOrEmpty(token))%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20context.Request.Headers.Add(%22Authorization%22%2C%20%22Bearer%20%22%20%2B%20token)%3B%0A%20%20%20%20%7D%0A%20%20%20%20await%20next()%3B%0A%7D)%3B%0A» message=»» highlight=»» provider=»manual»/]

В листинге 2 показан полный исходный код метода Configure — обратите внимание, как вы можете указать состояние сеанса, аутентификацию и маршрутизацию, которые будут использоваться.

[pastacode lang=»c» manual=»public%20void%20Configure(IApplicationBuilder%20app%2C%20IWebHostEnvironment%20env)%0A%7B%0A%20%20%20%20if%20(env.IsDevelopment())%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20app.UseDeveloperExceptionPage()%3B%0A%20%20%20%20%7D%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20else%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20app.UseExceptionHandler(%22%2FHome%2FError%22)%3B%0A%20%20%20%20%7D%0A%20%20%20%20app.UseSession()%3B%0A%20%20%20%20app.Use(async%20(context%2C%20next)%20%3D%3E%20%20%20%20%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20var%20token%20%3D%20context.Session.GetString(%22Token%22)%3B%20%0A%20%20%20%20%20%20%20%20if%20(!string.IsNullOrEmpty(token))%20%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20context.Request.Headers.Add(%22Authorization%22%2C%20%22Bearer%20%22%20%2B%20token)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20await%20next()%3B%0A%20%20%20%20%7D)%3B%0A%20%20%20%20app.UseStaticFiles()%3B%0A%20%20%20%20app.UseRouting()%3B%0A%20%20%20%20app.UseAuthentication()%3B%0A%20%20%20%20app.UseEndpoints(endpoints%20%3D%3E%20%20%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20endpoints.MapControllerRoute(name%3A%20%22default%22%2C%20pattern%3A%20%22%7Bcontroller%3DHome%7D%2F%7Baction%3DIndex%7D%2F%7Bid%3F%7D%22)%3B%20%0A%20%20%20%20%7D)%3B%0A%7D%0A» message=»Листинг 2: Метод Configure» highlight=»» provider=»manual»/]

Создание Views

Замените исходный код Index.cshtml кодом из листинга.

[pastacode lang=»java» manual=»%40model%20JWTAuth.Models.UserModel%0A%40%7B%0A%20%20%20%20ViewData%5B%22Title%22%5D%20%3D%20%22Index%22%3B%0A%7D%0A%0A%3Chr%20%2F%3E%0A%3Cdiv%20class%3D%22row%22%3E%20%20%0A%20%20%20%20%3Cdiv%20class%3D%22col-md-4%22%3E%0A%20%20%20%20%20%20%20%20%3Cform%20asp-action%3D%22Login%22%3E%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20asp-validation-summary%3D%22ModelOnly%22%20class%3D%22text-danger%22%3E%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22form-group%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Clabel%20asp-for%3D%22UserName%22%20class%3D%22control-%20label%22%3E%3C%2Flabel%3E%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%20asp-for%3D%22UserName%22%20class%3D%22form-control%22%20%2F%3E%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cspan%20asp-validation-for%3D%20%22UserName%22%20class%3D%22text-danger%22%3E%3C%2Fspan%3E%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22form-group%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Clabel%20asp-for%3D%22Password%22%20class%3D%22control-label%22%3E%3C%2Flabel%3E%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%20asp-for%3D%22Password%22%20type%3D%22password%22%20class%3D%22form-control%22%2F%3E%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cspan%20asp-validation-for%3D%22Password%22%20class%3D%22text-danger%22%3E%3C%2Fspan%3E%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22form-group%22%3E%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%20type%3D%22submit%22%20value%3D%22Login%22%20class%3D%22btn%20btn-primary%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3C%2Fform%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%0A%3C%2Fdiv%3E%0A%0A%40section%20Scripts%20%7B%40%7Bawait%20Html.RenderPartialAsync(%22_ValidationScriptsPartial%22)%3B%7D%7D%0A» message=»» highlight=»» provider=»manual»/]

Затем создайте представление с именем MainWindow.cshtml и напишите в нем следующий код:

[pastacode lang=»markup» manual=»%3Chtml%3E%0A%3Chead%3E%0A%20%20%20%20%3Cmeta%20name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%22%20%2F%3E%0A%20%20%20%20%3Ctitle%3ECodeMag%20JWT%20Demo%3C%2Ftitle%3E%0A%3C%2Fhead%3E%0A%0A%3Cbody%3E%0A%20%20%40%7B%0A%20%20%20%20%20%20ViewBag.Title%20%3D%20%22Demonstrating%20JWTs%20in%20ASP.NET%20Core%20MVC%205%22%3B%0A%20%20%7D%0A%0A%20%20%3Cp%3EYou’re%20logged%20in%20as%3A%26nbsp%3B%40User.Identity.Name%3C%2Fp%3E%0A%20%20%3Cp%3E%40ViewBag.Message%3C%2Fp%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E%0A» message=»» highlight=»» provider=»manual»/]

Наконец, создайте представление с именем Error.cshtml и напишите в нем следующий код:

[pastacode lang=»markup» manual=»%3Chtml%3E%0A%3Chead%3E%0A%20%20%20%20%3Cmeta%20name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%22%20%2F%3E%0A%20%20%20%20%3Ctitle%3EError…%3C%2Ftitle%3E%0A%3C%2Fhead%3E%0A%0A%3Cbody%3E%0A%20%20%3Cp%3E%40ViewBag.Message%3C%2Fp%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E%0A» message=»» highlight=»» provider=»manual»/]

Создайте класс UserRepository

Класс репозитория — это реализация шаблона проектирования репозитория, который управляет доступом к данным. Приложение использует экземпляр репозитория для выполнения операций CRUD с базой данных. В этом примере HomeController взаимодействует с UserRepository для получения пользователя на основе имени пользователя и пароля.

Создайте файл с именем IUserRepository.cs со следующим содержимым внутри:

[pastacode lang=»java» manual=»public%20interface%20IUserRepository%0A%7B%0A%20%20%20%20UserDTO%20GetUser(UserModel%20userMode)%3B%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Класс UserRepository расширяет интерфейс IUserRepository и реализует метод GetUser, как показано в листинге . Он также создает список объектов UserDTO. Обратите внимание, что пароль здесь жестко запрограммирован для простоты.

[pastacode lang=»java» manual=»public%20class%20UserRepository%20%3A%20IUserRepository%20%20%20%20%0A%7B%0A%20%20%20%20private%20readonly%20List%3CUserDTO%3E%20users%20%3D%20new%20List%3CUserDTO%3E()%3B%0A%0A%20%20%20%20public%20UserRepository()%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20users.Add(new%20UserDTO%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20UserName%20%3D%20%22joydipkanjilal%22%2C%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20Password%20%3D%20%22joydip123%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20Role%20%3D%20%22manager%22%20%0A%20%20%20%20%20%20%20%20%7D)%3B%20%20%20%20%0A%20%20%20%20%20%20%20%20users.Add(new%20UserDTO%20%0A%20%20%20%20%20%20%20%20%7B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20UserName%20%3D%20%22michaelsanders%22%2C%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20Password%20%3D%20%22michael321%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20Role%20%3D%20%22developer%22%20%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20users.Add(new%20UserDTO%20%0A%20%20%20%20%20%20%20%20%7B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20UserName%20%3D%20%22stephensmith%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Password%20%3D%20%22stephen123%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Role%20%3D%20%22tester%22%20%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20users.Add(new%20UserDTO%20%0A%20%20%20%20%20%20%20%20%7B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20UserName%20%3D%20%22rodpaddock%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Password%20%3D%20%22rod123%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20Role%20%3D%20%22admin%22%20%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20users.Add(new%20UserDTO%0A%20%20%20%20%20%20%20%20%7B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20UserName%20%3D%20%22rexwills%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Password%20%3D%20%22rex321%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20Role%20%3D%20%22admin%22%20%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%0A%20%20%20%20public%20UserDTO%20GetUser(UserModel%20userModel)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20return%20users.Where(x%20%3D%3E%20x.UserName.ToLower()%20%3D%3D%20userModel.UserName.ToLower()%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%26%26%20x.Password%20%3D%3D%20userModel.Password).FirstOrDefault()%3B%20%0A%20%20%20%20%7D%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Создайте класс TokenService

Создайте интерфейс ITokenService со следующим содержимым:

[pastacode lang=»java» manual=»public%20interface%20ITokenService%0A%7B%0A%20%20%20%20string%20BuildToken(string%20key%2C%20string%20issuer%2C%20UserDTO%20user)%3B%0A%20%20%20%20bool%20ValidateToken(string%20key%2C%20string%20issuer%2C%20string%20audience%2C%20string%20token)%3B%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Класс TokenService расширяет интерфейс ITokenService и реализует его методы, как показано в листинге .

[pastacode lang=»java» manual=»public%20class%20TokenService%20%3A%20ITokenService%0A%7B%0A%20%20%20%20private%20const%20double%20EXPIRY_DURATION_MINUTES%20%3D%2030%3B%0A%0A%20%20%20%20public%20string%20BuildToken(string%20key%2C%20string%20issuer%2C%20UserDTO%20user)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20var%20claims%20%3D%20new%5B%5D%20%7B%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20new%20Claim(ClaimTypes.Name%2C%20user.UserName)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20new%20Claim(ClaimTypes.Role%2C%20user.Role)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20new%20Claim(ClaimTypes.NameIdentifier%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Guid.NewGuid().ToString())%0A%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20var%20securityKey%20%3D%20new%20SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))%3B%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20credentials%20%3D%20new%20SigningCredentials(securityKey%2C%20SecurityAlgorithms.HmacSha256Signature)%3B%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20tokenDescriptor%20%3D%20new%20JwtSecurityToken(issuer%2C%20issuer%2C%20claims%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20expires%3A%20DateTime.Now.AddMinutes(EXPIRY_DURATION_MINUTES)%2C%20signingCredentials%3A%20credentials)%3B%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20new%20JwtSecurityTokenHandler().WriteToken(tokenDescriptor)%3B%20%20%0A%20%20%20%20%7D%0A%20%20%20%20public%20bool%20IsTokenValid(string%20key%2C%20string%20issuer%2C%20string%20token)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20var%20mySecret%20%3D%20Encoding.UTF8.GetBytes(key)%3B%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20mySecurityKey%20%3D%20new%20SymmetricSecurityKey(mySecret)%3B%0A%20%20%20%20%20%20%20%20var%20tokenHandler%20%3D%20new%20JwtSecurityTokenHandler()%3B%20%0A%20%20%20%20%20%20%20%20try%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20tokenHandler.ValidateToken(token%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20new%20TokenValidationParameters%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValidateIssuerSigningKey%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValidateIssuer%20%3D%20true%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValidateAudience%20%3D%20true%2C%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValidIssuer%20%3D%20issuer%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValidAudience%20%3D%20issuer%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20IssuerSigningKey%20%3D%20mySecurityKey%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%20out%20SecurityToken%20validatedToken)%3B%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20catch%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20true%3B%20%20%20%20%0A%20%20%20%20%7D%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Создайте класс HomeController

Наконец, мы подошли к классу контроллера. В классе HomeController вы воспользуетесь преимуществами внедрения зависимостей, чтобы иметь возможность использовать экземпляры классов Configuration, TokenService и UserRepository. Создайте следующие экземпляры только для чтения для каждого из трех интерфейсов, как показано ниже:

[pastacode lang=»java» manual=»private%20readonly%20IConfiguration_config%3B%0Aprivate%20readonly%20ITokenService_tokenService%3B%0Aprivate%20readonly%20IUserRepository_userRepository%3B%0A» message=»» highlight=»» provider=»manual»/]

Экземпляры ITokenService и IUserRepository создаются в методе ConfigureServices класса Startup, как показано в приведенном ниже фрагменте кода:

[pastacode lang=»java» manual=»services.AddScoped%3CITokenService%2C%20TokenService%3E()%3B%0Aservices.AddScoped%3CIUserRepository%2C%20UserRepository%3E()%3B%0A» message=»» highlight=»» provider=»manual»/]

Интерфейс IConfiguration объявлен в классе Startup как свойство только для чтения, как показано ниже:

[pastacode lang=»java» manual=»public%20IConfiguration%20Configuration%20%7B%20get%3B%20%7D%0A» message=»» highlight=»» provider=»manual»/]

Вот как внедрение конструктора используется в классе HomeController для каждого из описанных ранее экземпляров.

[pastacode lang=»java» manual=»public%20HomeController%20(IConfiguration%20config%2C%20ITokenService%20tokenService%2C%20IUserRepository%20userRepository)%0A%7B%0A%20%20%20%20_tokenService%20%3D%20tokenService%3B%0A%20%20%20%20_userRepository%20%3D%20userRepository%3B%0A%20%20%20%20_config%20%3D%20config%3B%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Полный исходный код класса HomeController приведен в листинге .

[pastacode lang=»java» manual=»public%20class%20HomeController%20%3A%20Controller%20%20%20%20%0A%7B%0A%20%20%20%20private%20readonly%20IConfiguration%20_config%3B%20%20%20%20%20%20%20%20%0A%20%20%20%20private%20readonly%20IUserRepository%20_userRepository%3B%20%20%20%20%20%20%20%0A%20%20%20%20private%20readonly%20ITokenService%20_tokenService%3B%20%20%0A%20%20%20%20private%20string%20generatedToken%20%3D%20null%3B%20%20%0A%20%20%20%20%0A%20%20%20%20public%20HomeController%20(IConfiguration%20config%2C%20ITokenService%20tokenService%2C%20IUserRepository%20userRepository)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20_config%20%3D%20config%3B%0A%20%20%20%20%20%20%20%20_tokenService%20%3D%20tokenService%3B%0A%20%20%20%20%20%20%20%20_userRepository%20%3D%20userRepository%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20public%20IActionResult%20Index()%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20return%20View()%3B%20%0A%20%20%20%20%7D%0A%0A%20%20%20%20%5BAllowAnonymous%5D%20%20%20%20%0A%20%20%20%20%5BRoute(%22login%22)%5D%20%20%20%20%0A%20%20%20%20%5BHttpPost%5D%20%0A%20%20%20%20public%20IActionResult%20Login(UserModel%20userModel)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20if%20(string.IsNullOrEmpty(userModel.UserName)%20%7C%7C%20string.IsNullOrEmpty(userModel.Password))%20%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20(RedirectToAction(%22Error%22))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20IActionResult%20response%20%3D%20Unauthorized()%3B%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20validUser%20%3D%20GetUser(userModel)%3B%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20if%20(validUser%20!%3D%20null)%20%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20generatedToken%20%3D%20_tokenService.BuildToken(_config%5B%22Jwt%3AKey%22%5D.ToString()%2C%20_config%5B%22Jwt%3AIssuer%22%5D.ToString()%2C%20validUser)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(generatedToken%20!%3D%20null)%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20HttpContext.Session.SetString(%22Token%22%2C%20generatedToken)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20RedirectToAction(%22MainWindow%22)%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20(RedirectToAction(%22Error%22))%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20(RedirectToAction(%22Error%22))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20private%20UserDTO%20GetUser(UserModel%20userModel)%20%20%20%20%20%20%20%20%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%2F%2F%20Write%20your%20code%20here%20to%20authenticate%20the%20user%20%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20_userRepository.GetUser(userModel)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%5BAuthorize%5D%0A%20%20%20%20%5BRoute(%22mainwindow%22)%5D%0A%20%20%20%20%5BHttpGet%5D%0A%20%20%20%20public%20IActionResult%20MainWindow()%20%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20string%20token%20%3D%20HttpContext.Session.GetString(%22Token%22)%3B%0A%20%20%20%20%20%20%20%20if%20(token%20%3D%3D%20null)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20(RedirectToAction(%22Index%22))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20if%20(!_tokenService.IsTokenValid(_config%5B%22Jwt%3AKey%22%5D.ToString()%2C%20_config%5B%22Jwt%3AIssuer%22%5D.ToString()%2C%20token))%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20(RedirectToAction(%22Index%22))%3B%20%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20ViewBag.Message%20%3D%20BuildMessage(token%2C%2050)%3B%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20View()%3B%20%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20IActionResult%20Error()%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20ViewBag.Message%20%3D%20%22An%20error%20occured…%22%3B%0A%20%20%20%20%20%20%20%20return%20View()%3B%20%20%0A%20%20%20%20%7D%0A%0A%20%20%20%20private%20string%20BuildMessage(string%20stringToSplit%2C%20int%20chunkSize)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20var%20data%20%3D%20Enumerable.Range(0%2C%20stringToSplit.Length%20%2F%20chunkSize).Select(i%20%3D%3E%20stringToSplit.Substring%20(i%20*%20chunkSize%2C%20chunkSize))%3B%0A%20%20%20%20%20%20%20%20string%20result%20%3D%20%22The%20generated%20token%20is%3A%22%3B%0A%20%20%20%20%20%20%20%20foreach%20(string%20str%20in%20data)%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%2B%3D%20Environment.NewLine%20%2B%20str%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20result%3B%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%20%20%20%20%0A» message=»» highlight=»» provider=»manual»/]

Метод BuildMessage используется для разделения сгенерированного токена на несколько строк. Метод GetUser класса HomeController вызывает метод GetUser класса UserRepository для получения экземпляра класса UserDTO на основе учетных данных пользователя, введенных на экране входа в систему.

Запустить приложение

Теперь запустите приложение, нажав Ctrl + F5 или просто F5. После того, как вы укажете учетные данные пользователя и нажмете «Войти», вы будете перенаправлены на другую веб-страницу, на которой отображается имя вошедшего в систему пользователя и сгенерированный токен

Резюме

JSON Web Token (JWT) — это открытый стандарт (RFC 7519), который определяет, как вы можете безопасно передавать информацию между двумя сторонами. Вы должны использовать SSL / TLS вместе с веб-токенами JSON (JWT) для борьбы с атаками типа «злоумышленник в середине». В большинстве случаев этого должно быть достаточно для шифрования полезной нагрузки перед ее передачей по сети. Вы также можете использовать JWT в качестве дополнительного уровня безопасности.

.