JWT‐токены - gro1vy/DeliverServiceAPI GitHub Wiki
В целях безопасности для защиты пользовательских данных в аккаунте в проекте используется механизм аутентификации на основе JWT токена, благодаря которому можно легко и просто организовать проверку на права доступа к методам контроллеров приложения asp.net. Именно из-за простоты использования был выбран такой механизм.
JWT (JSON Web Token) - это структурированный формат для представления информации между двумя сторонами в виде объекта JSON. JWT часто используется для аутентификации и авторизации в веб-приложениях и API. Он представляет собой компактный и самодостаточный способ передачи информации между сторонами в зашифрованной форме.
JWT состоит из трех частей, разделенных точками:
- Заголовок (Header): В заголовке указывается тип токена и алгоритм, используемый для подписи токена. Заголовок обычно выглядит как:
json
{
"alg": "HS256",
"typ": "JWT"
}
- Полезная нагрузка (Payload): Полезная нагрузка содержит информацию, которую вы хотите закодировать в токене. Это могут быть утверждения (claims) о пользователе, правах доступа и другие данные. Пример полезной нагрузки:
json
{
"name": "John Doe", // имя пользователя
"exp": 1516239022 // время истечения токена (в формате UNIX-времени)
}
- Подпись (Signature): Подпись создается путем объединения заголовка и полезной нагрузки и последующим хешированием с использованием секретного ключа. Это обеспечивает проверку подлинности токена. Пример подписи:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
Подробнее познакомится со структурой JWT токена поможет JWT Decoder.
Первым делом необходимо установить необходимой пакет, который показан на картинке, в Nuget.
Далее в appsettings.json добавить секретный ключ:
"ApiSettings": {
"Secret": "Any Secret Key"
}
После чего в Program.cs необходимо настроить аутентификации, в примере ниже сначал происходит настройка схемы аутентификации, после чего настройка самого JWT-токена. Поле RequireHttpsMetadata указывает должен ли использоваться HTTPS или нет, поле TokenValidationParameters имеет несколько внутренних полей для настройки валидации, некторые из них представлены в примере. ValidateIssuerSigningKey говорит, нужно ли проверять ключ подписи токена или нет, если нужно, то в IssuerSigningKey указывает секретный ключ, созданный ранее в appsettings.json, причем сначала он декодируется в битовое представление и передается в SymmetricSecurityKey. Соответственно ValidateIssuer говорит, стоит ли проверять издателя токена, и если да, то в ValidIssuer указываем издателя, с остальными параметрами TokenValidationParameters все работает так же.
var key = builder.Configuration.GetValue<string>("ApiSettings:Secret");
builder.Services.AddAuthentication(authOptions =>
{
authOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtOptions =>
{
jwtOptions.RequireHttpsMetadata = false; // Develop
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)),
ValidateIssuer = true,
ValidIssuer = "HITs",
ValidateAudience = false
};
});
Далее в Program.cs необходимо также добавить middleware для аутентификации:
app.UseAuthentication();
Для того, чтобы добавить в Swagger возможность работать с аутентификацией, необходимо добавить следующий код:
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "",
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string> ()
}
});
});
После настройки проекта для JWT аутентификации, научимся создавать токены. Для этого нужно создать JwtSecurityTokenHandler, благодаря которому будет сгенерирован токен, и SecurityTokenDescriptor, в котором будет проходить описание токена. В Subject, который является объектом типа ClaimsIdentity, происходит перечисление утверждений (полезной информации), в примере созданы Claim для имя пользователя и его роли. В Expires указывается, сколько будет действителен токен, в SigningCredentials указаны секретный ключ из appsettings.json и алгоритм подписи в данном примере это SHA256, а в Issuer указывает издатель. Это лишь некоторые настройки SecurityTokenDescriptor. И в конце происходит генерация токен с помощью ранее созданного JwtSecurityTokenHandler.
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(key);
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.UserName.ToString()),
new Claim(ClaimTypes.Role, user.Role),
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new (new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Issuer = "HITs"
};
var token = tokenHandler.CreateToken(tokenDescriptor);
Теперь, если у нас есть контроллер и мы хотим защитить его методы авторизацией, то рядом с методом прописываем атрибут [Authorize], тогда если кто-то захочет обратиться к данному методу, то он должен отправить JWT-токен вместе с запросом, если токен действителен, то пользователь попадет в метод, иначе выдаст код 401. Также если мы хотим проверить роль пользователя, то в том же атрибуте должны указать проверку [Authorize(Roles = "AnyRoles")], если роль пользователя не та, то выдаст код 403.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private WeatherForecastService _weatherForecastService;
public WeatherForecastController(WeatherForecastService weather)
{
_weatherForecastService = weather;
}
[HttpGet]
[Authorize]
public ActionResult<IEnumerable<WeatherForecastDTO>> Get()
{
return _weatherForecastService.GenerateWeatherForecasts();
}
[HttpPost]
[Authorize(Roles = "admin")]
public async Task Post(WeatherForecastDTO model)
{
await _weatherForecastService.Add(model);
}
}