Ответ
В проекте использовалась аутентификация на основе JWT (JSON Web Tokens) по схеме Bearer Token в связке с Refresh Tokens для баланса безопасности и удобства пользователя.
Архитектура потока
- Логин: Клиент отправляет
POST /api/auth/loginс учетными данными. - Верификация: Сервер проверяет логин/пароль (обычно против хэша в БД).
- Генерация токенов:
- Access Token (JWT): Короткоживущий (15-30 мин), содержит claims (userId, roles, permissions). Подписывается секретным ключом.
- Refresh Token: Долгоживущий (7-30 дней), хранится в БД в связке с userId, IP-адресом (опционально) и может быть отозван.
- Ответ: Сервер возвращает оба токена клиенту.
- Доступ к API: Клиент прикладывает Access Token в заголовке
Authorization: Bearer <token>к каждому запросу. - Обновление: По истечении срока Access Token, клиент использует Refresh Token для вызова
POST /api/auth/refreshи получения новой пары токенов.
Реализация в ASP.NET Core
Конфигурация сервисов (Program.cs / Startup.cs):
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Jwt:Audience"],
ValidateLifetime = true, // Критически важно!
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])
),
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero // Убираем запас времени для точной проверки срока.
};
// Для аутентификации из WebSocket или SignalR соединений
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// Извлекаем токен из query string для WebSockets
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireClaim("role", "Admin"));
});
Генерация токена (пример):
public string GenerateAccessToken(User user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim("role", user.Role),
new Claim("custom_claim", "some_value")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(30), // Короткая жизнь
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Защита endpoint'ов:
[ApiController]
[Route("api/[controller]")]
[Authorize] // Все методы контроллера требуют аутентификации
public class SecureController : ControllerBase
{
[HttpGet("admin-data")]
[Authorize(Policy = "RequireAdminRole")] // Дополнительная проверка роли
public IActionResult GetAdminData() { ... }
[HttpGet("profile")]
public IActionResult GetProfile()
{
// Получение ID текущего пользователя из токена
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
...
}
}
Дополнительные меры безопасности
- HTTPS: Обязательное использование для всего трафика.
- Хранение на клиенте: Access Token хранится в памяти (переменная JS), а не в
localStorage, чтобы избежать XSS-атак. Refresh Token хранится вHttpOnlycookie для защиты от XSS. - Отзыв токенов: При смене пароля или выходе (logout) Refresh Token удаляется/инвалидируется на сервере.
- Валидация: Проверка срока действия, подписи, издателя и аудитории.