Skip to content

Sprint Backend 04. Jwt

IOF edited this page Dec 13, 2025 · 5 revisions
  • проверьте фиксацию изменений в master
  • создайте ветку sprint4

Авторизация

Создание метода авторизации в API

  • добавьте сингнатуру в интерфейс IUserRepository и метод User FindUserByLogin(string login)
  • удалите реализацию UserMemoryRepository. Можно не удалять, но тогда надо реализовывать все новые методы интерфейса или делать заглушки
  • реализуйте логику метода в UserRepository
  • создайте метод POST /api/Users/Login в UsersController.
    [HttpPost("Login")]
    public ActionResult<UserDto> Login(LoginDto loginDto)
    {
        var user = _userRepo.FindUserByLogin(loginDto.Login);
        return CheckPasswordHash(loginDto, user);
    }
  • проверку логики пароля вынесите в отдельный приватный метод, который поместите в Users Controller
      private ActionResult<UserDto> CheckPasswordHash(LoginDto loginDto, User user)
    {
        using var hmac = new HMACSHA256(user.PasswordSalt);
        var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password));

        for (int i = 0; i < computedHash.Length; i++)
        {
            if (computedHash[i] != user.PasswordHash[i])
            {
                return Unauthorized($"Неправильный пароль");
            }
        }

        return Ok(user.ToDto());
    }
  • проверьте работу метода аутентификации по логину в swagger
image
  • добавьте разрешения CORS для доступа к API

Program.cs

app.UseCors(o => o.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
  • в проекте API создайте интерфейс ITokenService
    public interface ITokenService
    {
        string CreateToken(string UserLogin);
    }
  • установите пакет Microsoft.IdentityModel.Tokens и System.IdentityModel.Tokens.Jwt:
  <ItemGroup>
    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.15.0" />
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
  </ItemGroup>
  • реализуйте сервис TokenService для генерации jwt - токена в папке Services.
public class TokenService : ITokenService
{

    private readonly SymmetricSecurityKey _key;
    public TokenService(IConfiguration config)
    {
      _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenPublicKey"]!));
    }

    public string CreateToken(string UserLogin)
    {
       var claims =  new List<Claim>{
         new Claim(JwtRegisteredClaimNames.Name, UserLogin)
       };

       var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);

       var tokenDecriptor = new SecurityTokenDescriptor(){
         Subject = new ClaimsIdentity(claims),
         Expires = DateTime.UtcNow.AddDays(7),
         SigningCredentials = creds
       };

       var tokenHandler = new JwtSecurityTokenHandler();
       var token = tokenHandler.CreateToken(tokenDecriptor);
       return tokenHandler.WriteToken(token);

    }
}

Примечание: значение config["TokenPublicKey"] мы получаем из конфигурации файла appsettings.json. Это открытый ключ шифрования. Добавьте данную настройку после настроек логгирования.

appsettings.json

"TokenPublicKey":"tokentokentokentokentokentokentokentokentokentokentokentokentokentoken"

Изменение модели данных User

  • в модель User добавьте новое свойство public string Token {get; set;}.
  • в классе SampleAppContext в методе OnModelCreating обновите конфигурацию модели User: entity.Property(e => e.Token).IsRequired();
  • создайте миграцию и примените для обновления базы данных.
  • зарегистрируйте в контейнер DI сервис TokenService c областью видимости Scoped.
  • внедрите объект ITokenService в конструктор UsersController.
  • примените метод генерации токена из сервиса к полю Token у пользователя.
  • добавьте токен в модель UserDto
  • обновите новым свойством маппер UserMapper
  • проверьте конечную точку создания пользователя: у пользователя токен
  • обновите SeedsController, чтобы он создавал пользователей с токенами

Authentication

  • установите пакет Microsoft.AspNetCore.Authentication.JwtBearer -v 9.0.0

Провека пакетов для Swagger

SampleApp.API.csproj

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.0" />
  </ItemGroup>

Program.cs

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.SaveToken = true;
        options.RequireHttpsMetadata = false;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = true,

            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["TokenPublicKey"]!)),
        };
    });

Настройка Swagger для работы с jwt

  • добавьте новые параметры AddSwaggerGen:
builder.Services.AddSwaggerGen(c =>
{
    ...
   

c.AddSecurityDefinition(
       c.AddSecurityDefinition(
        "Bearer",
        new OpenApiSecurityScheme
        {
            Description = "Authorization using jwt token. Example: \"Bearer {token}\"",
            Name = "Authorization",
            In = ParameterLocation.Header,
            Type = SecuritySchemeType.ApiKey,
        }
    );
    c.AddSecurityRequirement(
        new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer",
                    },
                },
                new string[] { }
            },
        }
    ); "Bearer",
        new OpenApiSecurityScheme
        {
            Description = "Authorization using jwt token. Example: \"Bearer {token}\"",
            Name = "Authorization",
            In = ParameterLocation.Header,
            Type = SecuritySchemeType.ApiKey,
        }
    );
    c.AddSecurityRequirement(
        new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer",
                    },
                },
                new string[] { }
            },
        }
    );
   
});
  • зарегистрируйте сервис авторизации builder.Services.AddAuthorization();, а затем подключите его в конвейер сразу после процесса аутентификации
app.UseAuthentication();
app.UseAuthorization();
  • для удобного тестирования jwt создайте отдельный TokenController
[ApiController]
[Route("api/[controller]")]
public class TokenController : ControllerBase
{
    private readonly IConfiguration _config;
    public TokenController(IConfiguration config)
    {
        _config = config;
    }

    [HttpGet]
    public IActionResult GenerateToken()
    {
        var claims = new List<Claim> { new Claim(ClaimTypes.Name, "user") };
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["TokenPublicKey"]!));

        // создаем JWT-токен
        var jwt = new JwtSecurityToken(
            // issuer: _config["Jwt:Issuer"],
            // audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.Add(TimeSpan.FromDays(365)),
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

        return Ok(new JwtSecurityTokenHandler().WriteToken(jwt));
    }
}
  • чтобы защитить конечную точку надо поставить атрибут [Authorize]. Теперь, чтобы получить доступ по конечной точке надо передать в запросе заголовка Authorization валидный jwt-токен формата: Bearer {token}. Проверить валидность токена можно на сайте - jwt.io. Тестирование удобно делать в Postman.
    [Authorize]
    [HttpGet]
    public ActionResult GetUser(){
        return Ok(_repo.GetUsers());
    }
image
  • отправьте запрос с jwt токеном в запросе с помощью Swagger
image
  • введите в поле значение Bearer token
image

Задание: протестируйте конечную точку создания пользователя в jwt-аутентификацией в Postman и Swagger

image

Extensions. Методы расширения

  • создайте папку Extensions, в которой создайте статический класс JwtServices
public static class JwtServices
{
    public static IServiceCollection AddJwtServices(
        this IServiceCollection services,
        IConfiguration configuration
    )
    {
        services.AddSwaggerGen(c =>
        {
            c.EnableAnnotations();
            c.SwaggerDoc(
                "v1",
                new OpenApiInfo
                {
                    Title = "SampleApp",
                    Version = "v1",
                    Description = "API для пользователей",
                    Contact = new OpenApiContact
                    {
                        Url = new Uri("http://prep.scc/~asv"),
                        Email = "asv@prep.scc",
                    },
                }
            );

            c.AddSecurityDefinition(
                "Bearer",
                new OpenApiSecurityScheme
                {
                    Description = "Authorization using jwt token. Example: \"Bearer {token}\"",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                }
            );
            c.AddSecurityRequirement(
                new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer",
                            },
                        },
                        new string[] { }
                    },
                }
            );
        });
        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = true,

                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(configuration["TokenPublicKey"]!)
                    ),
                };
            });
        services.AddAuthorization();

        return services;
    }
}

Теперь мы вынесем эту логику в отдельный файл и можем подключить это все одним сервисом.

builder.Services.AddJwtServices(builder.Configuration);

Примечание: настройте уровень логирования для отслеживания процесса работы с jwt в файле appsettings.json

"Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.Authentication": "Debug"
    }
  }

Замечание: быстрое генерирование jwt

dotnet user-jwts create

Для продакшена используйте ручную генерацию через System.IdentityModel.Tokens.Jwt.

Примечание: данные пользователя можно получать по токену

Commit

  • git add -A
  • git commit - m "Выполнен sprint4"
  • git switch master
  • git rebase sprint4

Clone this wiki locally