Содержание
Одной из задач аутентификации в приложении 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
довольно обширная, поэтому в следующих частях мы продолжим знакомиться с этими классами и их использованием для авторизации.