Содержание
Одной из задач аутентификации в приложении ASP.NET Core является установка личности пользователя, который представляется в приложении свойством User класса HttpContext. В этом свойстве могут передаваться различные сведения о пользователе — имя, логин, принадлежность к определенной группе пользователей и т.д., которые, в дальнейшем, могут использоваться в приложении для авторизации. В этой части мы продолжим работу над приложением, демонстрирующем аутентификацию и авторизацию с использованием JWT и разберемся, как можно использовать свойство HttpContext.User
Свойство HttpContext.User и класс ClaimsPrincipal
Свойство HttpContext.User определено следующим образом:
public abstract ClaimsPrincipal User { get; set; }
Класс ClaimsPrincipal, в свою очередь, содержит следующие свойства, относящиеся к личности пользователя
Claims |
Коллекция всех утверждений (свойств пользователя) из всех удостоверений, связанных с пользователем. |
Identities |
Коллекция удостоверений, связанных с пользователем. |
Identity |
Основное удостоверение, связанное с этим пользователем. |
Таким образом, с одним пользователем могут быть связаны различные удостоверения (Identity). Каждое удостоверение представляет собой объект, реализующий интерфейс IIdentity со следующими свойствами:
Authentication |
Тип используемой аутентификации. |
Is |
Значение, определяющее, прошел ли пользователь проверку подлинности. |
Name |
Имя текущего пользователя. |
Для каждого удостоверения (Identity) могут задаваться различные утверждения (Claims), позволяющие более полно идентифицировать пользователя.
Работа с HttpContext.User при использовании JWT
До этого момента мы никак не касались свойства User при разработке нашего приложения, демонстрирующего аутентификацию и авторизацию пользователя. Теперь, немного разобравшись с этим свойством, мы можем его использовать. Код приложения, с которым мы будем сейчас работать, можно посмотреть здесь.
Допишем класс пользователя следующим образом:
public class User
{
public string Login { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
public User(string login, string password, string name, string email, int age)
{
Login = login;
Password = password;
Name = name;
Email = email;
Age = age;
}
public IEnumerable<Claim> GetUserClaims()
{
return new List<Claim>
{
new Claim(ClaimTypes.Name, Name, ClaimValueTypes.String),
new Claim(ClaimTypes.Email, Email, ClaimValueTypes.Email),
new Claim("Age", Age.ToString(), ClaimValueTypes.Integer)
};
}
}
Здесь мы добавили в класс новые свойства — имя, адрес электронной почты и возраст, добавили новый конструктор, а также метод GetUserClaims, который возвращает список утверждений (Claim) для текущего пользователя. Для демонстрации, мы формируем этот список на основании трех свойств класса User. Теперь передадим данные о пользователе в токен.
Добавим пользователей в условную БД
//условная база данных с учетными записями пользователей
List<User> UserDatabase = new()
{
new User("user","12345","Иванов Иван Иванович", "user@mail.ru", 17),
new User("admin","root","Петров Петр Петрович", "admin@mail.ru", 25),
};
В нашем приложении токен генерируется с использованием следующего конструктора:
public JwtSecurityToken(string issuer = null, string audience = null, IEnumerable<Claim> claims = null, DateTime? notBefore = null, DateTime? expires = null, SigningCredentials signingCredentials = null)
используя при этом только несколько параметров:
JwtSecurityToken token = new(
issuer: authOptions?.Issuer,
audience: authOptions?.Audience,
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(authOptions.TokenLifetime)),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
Добавим в конструктор список, который мы получаем из метода GetUserClaims:
JwtSecurityToken token = new(
issuer: authOptions?.Issuer,
audience: authOptions?.Audience,
claims: user.GetUserClaims(), //добавляем список Claim в токен
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(authOptions.TokenLifetime)),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
Теперь, если мы передаем токен в заголовке входящего запроса, то ASP.NET Core получит из токена все свойства пользователя и вернет их в свойстве HttpContext.User. Изменим конечную точку «/» следующим образом:
app.MapGet("/", (HttpContext context) =>
{
if ((context.User != null) && (context.User.Identity.IsAuthenticated))
{
var claims = context.User.Claims;
var name = claims.FirstOrDefault(f => f.Type == ClaimTypes.Name);
return name != null ? $"Привет, {name.Value}" : $"Привет, аноним";
}
else
return "Вы не аутентифицированы";
});
здесь мы проверяем, определено ли свойство HttpContext.User и аутентифицирован ли пользователь. Если пользователь аутентифицирован, то мы получаем список всех утверждений:
var claims = context.User.Claims;
и пытаемся найти в этом списке имя пользователя:
var name = claims.FirstOrDefault(f => f.Type == ClaimTypes.Name);
если имя найдено, то здороваемся с пользователем по имени, иначе — как с анонимом:
return name != null ? $"Привет, {name.Value}" : $"Привет, аноним";
если же свойство HttpContext.User не определено или пользователь не аутентифицирован, то сообщаем об этом пользователю:
return "Вы не аутентифицированы";
Проверим работу нашего приложения. Так выглядит главная страница приложения после запуска:
Теперь аутентифицируем пользователя:
Перейдем снова на главную страницу приложения
страница доступа только для авторизованных пользователей:
Как можно видеть, мы получаем сведения о пользователе непосредственно из JWT-токена, записанного в заголовке авторизации без его расшифровки вручную — ASP.NET Core делает это самостоятельно. Остается привести исходный код всего проекта.
Исходный код проекта
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Xml.Linq;
namespace AspAuth
{
public class AuthOptions
{
public string Issuer { get; set; } //издатель токена
public string Audience { get; set; } //потребитель токена
public string SecretKey { get; set; } //секретный ключ для подписи
public bool ValidateAudience { get; set; } //проверять потребителя
public bool ValidateIssuer { get; set; } //проверять издателя
public bool ValidateLifetime { get; set; } //проверять время жизни токена
public bool ValidateIssuerSigningKey { get; set; } //проверять подпись
public int TokenLifetime { get; set; } //время жизни токена в минутах
}
public class User
{
public string Login { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
public User(string login, string password, string name, string email, int age)
{
Login = login;
Password = password;
Name = name;
Email = email;
Age = age;
}
public IEnumerable<Claim> GetUserClaims()
{
return new List<Claim>
{
new Claim(ClaimTypes.Name, Name, ClaimValueTypes.String),
new Claim(ClaimTypes.Email, Email, ClaimValueTypes.Email),
new Claim("Age", Age.ToString(), ClaimValueTypes.Integer)
};
}
}
public class Program
{
public static void Main(string[] args)
{
//условная база данных с учетными записями пользователей
List<User> UserDatabase = new()
{
new User("user","12345","Иванов Иван Иванович", "user@mail.ru", 17),
new User("admin","root","Петров Петр Петрович", "admin@mail.ru", 25),
};
var builder = WebApplication.CreateBuilder(args);
var authOptions = builder.Configuration.GetSection("Jwt").Get<AuthOptions>();
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authOptions.SecretKey));
builder.Services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = authOptions.ValidateIssuer,
ValidateAudience = authOptions.ValidateAudience,
ValidateLifetime = authOptions.ValidateLifetime,
ValidateIssuerSigningKey = authOptions.ValidateIssuerSigningKey,
ValidIssuer = authOptions.Issuer,
ValidAudience = authOptions.Audience,
IssuerSigningKey = key
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.Use(async (context, next) =>
{
if (context.Request.Cookies.TryGetValue("token", out string? token))
context.Request.Headers.Authorization = $"Bearer {token}";
await next();
});
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (HttpContext context) =>
{
if ((context.User != null) && (context.User.Identity.IsAuthenticated))
{
var claims = context.User.Claims;
var name = claims.FirstOrDefault(f => f.Type == ClaimTypes.Name);
return name != null ? $"Привет, {name.Value}" : $"Привет, аноним";
}
else
return "Вы не аутентифицированы";
});
app.MapGet("/auth", [Authorize] (HttpContext context) =>
{
return Results.Ok(authOptions);
});
app.MapGet("/login", (string? login, string? password, HttpContext context) =>
{
if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password))
return Results.BadRequest(new { Error = "Не задан логин или пароль" });
var user = UserDatabase.FirstOrDefault(u => (u.Login == login) && (u.Password == password));
if (user == null)
return Results.BadRequest(new { Error = "Не верно введен логин или пароль" });
JwtSecurityToken token = new(
issuer: authOptions?.Issuer,
audience: authOptions?.Audience,
claims: user.GetUserClaims(), //добавляем список Claim в токен
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(authOptions.TokenLifetime)),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
string tokenString = handler.WriteToken(token);
context.Response.Cookies.Append("token", tokenString);//отправляем клиенту куку с токеном
return Results.Ok(new { token = tokenString, user = login });
});
app.Run();
}
}
}
Итого
В этой части мы разобрались с некоторыми свойствами объекта HttpContext.User и научились устанавливать и получать утверждения (Claim), относящиеся к конкретному пользователю. Тема использования авторизации с использованием классов Claim, ClaimsPrincipal и ClaimIdentity довольно обширная, поэтому в следующих частях мы продолжим знакомиться с этими классами и их использованием для авторизации.



