В этой статье мы узнаем, как создать и настроить аутентификацию JWT Bearer для авторизации API с помощью .NET 5.0. Есть много ресурсов, которые раскрывают тему «JWT Auth», но в этой статье мы будем сосредоточены на реализации пользовательской аутентификации JWT с кастомным промежуточным программным обеспечением и атрибутом авторизации.
JWT аутентификация
Необходимые инструменты:
Visual Studio 2019 - Можно загрузить здесь .Net 5.0 SDK - Можно загрузить здесь
План работы:
- Настройка проекта веб-API .NET 5.0.
- Настройка аутентификацию JWT
- Генерация токена JWT.
- Валидация токена JWT, используя кастомное промежуточное ПО и атрибут авторизации.
- Тестирование API с использованием Swagger.
Настройка проект .Net 5.0 Web API
Откройте Visual Studio, выберите «Создать новый проект» и нажмите кнопку «Далее».
Добавьте «Имя проекта» и «Имя решения» также выберите путь, чтобы сохранить проект в этом месте, нажмите «Далее».
Теперь выберите целевой фреймворк .NET 5.0, что мы получаем, как только мы устанавливаем SDK, а также получим еще один вариант для настройки поддержки OpenAPI по умолчанию с помощью этого флажка.
Настройка JWT-аутентификации
Чтобы настроить JWT (JSON веб-токен), у нас должен быть установлен пакет Nuget внутри проекта, поэтому давайте сначала добавим зависимости проекта.
Пакеты NuGet для установки
Внутри Visual Studio - нажмите на Tools -> Nuget Package Manager -> Manage Nuget packages for solution.
Установить через консоль.
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 5.0.7
Первый шаг - настроить аутентификацию JWT в нашем проекте. Для этого нам нужно зарегистрировать схему JWT в Swagger Service, используя метод Addauthentication
.
Давайте определим службу SWARGER и закрепим за ней авторизацию по JWT.
#region Swagger Configuration
services.AddSwaggerGen(swagger =>
{
//This is to generate the Default UI of Swagger Documentation
swagger.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "JWT Token Authentication API",
Description = "ASP.NET Core 5.0 Web API"
});
// To Enable authorization using Swagger (JWT)
swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
});
swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
#endregion
Добавим службу для выполнения аутентификации, а также вызовем AddJWTBearer
для конфигурации авторизации.
#region Authentication
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) //Configuration["JwtToken:SecretKey"]
};
});
#endregion
В приведенном выше примере мы указали параметры, которые необходимо учитывать для проверки токена.
- Проверка на сервере (
Validateissuer = true
), который генерирует токен. - Проверка получателя токена, авторизован ли он для получения токена (
ValidateAudience = True
) - Проверка, не истек ли токен и валиден ли ключ подписания эмитента (
ValidateLifetime = True
) - Проверка подписи токена (
ValidateissuerSigningKey = True
)
Мы должны указать значения для Audience
, Issuer
и Secret key
в этом проекте, мы сохранили эти значения внутри файла appsettings.json
.
appsettings.json
"Jwt": {
"Key": "SecretKey10125779374235322",
"Issuer": "https://localhost:44341",
"Audience": "http://localhost:4200"
},
Генерация JWT токена
Давайте создадим контроллер с именем AuthController
внутри папки controllers
, и добавим метод Auth
, который отвечает для проверку учетных данных входа и создадим токен на основе имени пользователя. Мы отметили этот метод с помощью атрибута AllowAnonymous
для обхода аутентификации. Этот метод ожидает объект LoginModel
для имени пользователя и пароля.
Мы создали директорию Services
, в которой находится наша основная бизнес-логика, и мы используем внедрение зависимостей для использования этих служб в контроллере.
В демонстрационных целях я жестко запрограммировал значения внутри метода для проверки модели.
UserService.cs
public bool IsValidUserInformation(LoginModel model)
{
if (model.UserName.Equals("Jay") && model.Password.Equals("123456")) return true;
else return false;
}
IUserService.cs
public interface IUserService
{
bool IsValidUserInformation(LoginModel model);
}
Добавьте эту службу в класс Startup
в методе ConfigureServices
.
services.AddTransient<IUserService,UserService>();
Внутри AuthController
создадим приватный метод, известный как GenerateJWTToken
, чтобы создать токен на основе эмитента, потребителя и секретного ключа, которые мы определили в файле appSettings.json
.
using JWTAuth_Validation.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace JWTAuth_Validation.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IUserService _userService;
public AuthController(IConfiguration configuration,IUserService userService)
{
_configuration = configuration;
_userService = userService;
}
[AllowAnonymous]
[HttpPost(nameof(Auth))]
public IActionResult Auth([FromBody] LoginModel data)
{
bool isValid = _userService.IsValidUserInformation(data);
if (isValid)
{
var tokenString = GenerateJwtToken(data.UserName);
return Ok(new { Token = tokenString, Message = "Success" });
}
return BadRequest("Please pass the valid Username and Password");
}
[Authorize(AuthenticationSchemes = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)]
[HttpGet(nameof(GetResult))]
public IActionResult GetResult()
{
return Ok("API Validated");
}
/// <summary>
/// Generate JWT Token after successful login.
/// </summary>
/// <param name="accountId"></param>
/// <returns></returns>
private string GenerateJwtToken(string userName)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", userName) }),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = _configuration["Jwt:Issuer"],
Audience = _configuration["Jwt:Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
#region JsonProperties
/// <summary>
/// Json Properties
/// </summary>
public class LoginModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
}
#endregion
}
Как только мы включили аутентификацию, я создал образец Get API, добавив атрибут авторизации, чтобы этот API выполнил проверку токена, передаваемого с помощью HTTP-запроса.
Если кто-то попытается получить доступ к этому API без надлежащего токена, он выдаст 401 Unauthorized Access
в качестве ответа. Если мы хотим обойти аутентификацию для любого из наших существующих методов, мы можем пометить этот метод атрибутом AllowAnonymous
.
Проверка JWT токена, используя middleware и атрибут Authorization
Ниже показано настраиваемое промежуточное ПО JWT, которое проверяет токен в заголовке запроса Authorization
, если он существует. При успешной проверке промежуточное ПО извлекает этого связанного пользователя из базы данных и закрепляет его за своим контекстом. context.Items["User"]
делает текущую учетную запись доступной для любого другого кода, работающего в текущей области запроса, который мы будем использовать ниже в настраиваемом атрибуте авторизации.
Создайте папку с именем Middleware
, в которой добавьте классы JWTMiddleware
и AuthorizeAttribute
.
JWTMiddleware.cs
using JWTAuth_Validation.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace JWTAuth_Validation.Middleware
{
public class JWTMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
private readonly IUserService _userService;
public JWTMiddleware(RequestDelegate next, IConfiguration configuration, IUserService userService)
{
_next = next;
_configuration = configuration;
_userService = userService;
}
public async Task Invoke(HttpContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
attachAccountToContext(context, token);
await _next(context);
}
private void attachAccountToContext(HttpContext context, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var accountId = jwtToken.Claims.First(x => x.Type == "id").Value;
// attach account to context on successful jwt validation
context.Items["User"] = _userService.GetUserDetails();
}
catch
{
// do nothing if jwt validation fails
// account is not attached to context so request won't have access to secure routes
}
}
}
}
AuthorizeAttribute.cs
using JWTAuth_Validation.Controllers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
namespace JWTAuth_Validation.Middleware
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var account = (LoginModel)context.HttpContext.Items["User"];
if (account == null)
{
// not logged in
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
}
}
}
}
Давайте внедрим это промежуточное ПО в класс Startup
.
app.UseMiddleware<JWTMiddleware>();
Теперь каждый раз, когда кто то попадает в API с заголовком авторизации, промежуточное ПО сначала проверит токен и отправляет соответствующую информацию в ответ на запрос.
Тестирование API с помощью Swagger (OpenAPI)
Запустите приложение. Это приведет нас к странице Swagger Index
со всей настроенной конфигурацией, которую мы сделали в проекте.
Давайте передадим действительные учетные данные Auth API, чтобы получить токен доступа.
{
"userName": "Jay",
"password": "123456"
}
Скопируйте токен и добавьте тот же токен в форме авторизации, а затем нажмите Authorize
.
Теперь всё API авторизовано в Swagger. Давайте проверим API Get.
Заключение
JWT давно известен в веб-разработке. Это открытый стандарт, который позволяет безопасно и компактно передавать данные для авторизации в виде объекта JSON. В этой статье мы узнали, как создать и проверить JWT с помощью основного приложения ASP.NET.