JWT на .NET Core 2.0
Я был довольно приключенческим, чтобы заставить JWT работать над DotNet core 2.0 (теперь он достиг финальной версии сегодня). Существует тонна документации, но весь образец кода, похоже, использует устаревшие API и подходит к Core, он позитивно головокружительно, чтобы понять, как именно он должен быть реализован. Я пробовал использовать Хосе, но приложение. UseJwtBearerAuthentication устарела, и нет документации о том, что делать дальше.
Есть ли у кого-нибудь проект с открытым исходным кодом, который использует dotnet core 2.0, который может просто анализировать JWT из заголовка авторизации и разрешать мне разрешать запросы на токен JWT, кодированный HS256?
В приведенном ниже классе не выбрасываются никакие исключения, но запросы не разрешены, и я не вижу никаких указаний, почему они неавторизированы. Ответы пустые 401, так что для меня это означает, что не было никаких исключений, но секрет не соответствует.
Одна вещь заключается в том, что мои токены зашифрованы алгоритмом HS256, но я не вижу индикатора, чтобы заставить его использовать этот алгоритм где угодно.
Вот класс, который у меня есть до сих пор:
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace Site.Authorization
{
public static class SiteAuthorizationExtensions
{
public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
{
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKeys = new List<SecurityKey>{ signingKey },
// Validate the token expiry
ValidateLifetime = true,
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.IncludeErrorDetails = true;
o.TokenValidationParameters = tokenValidationParameters;
o.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = c =>
{
c.NoResult();
c.Response.StatusCode = 401;
c.Response.ContentType = "text/plain";
return c.Response.WriteAsync(c.Exception.ToString());
}
};
});
return services;
}
}
}
Ответы
Ответ 1
My tokenValidationParameters
работает, когда они выглядят следующим образом:
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = GetSignInKey(),
ValidateIssuer = true,
ValidIssuer = GetIssuer(),
ValidateAudience = true,
ValidAudience = GetAudience(),
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
и
static private SymmetricSecurityKey GetSignInKey()
{
const string secretKey = "very_long_very_secret_secret";
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
return signingKey;
}
static private string GetIssuer()
{
return "issuer";
}
static private string GetAudience()
{
return "audience";
}
Кроме того, добавьте options.RequireHttpsMetadata = false, как это:
.AddJwtBearer(options =>
{
options.TokenValidationParameters =tokenValidationParameters
options.RequireHttpsMetadata = false;
});
ИЗМЕНИТЬ
Не забудьте позвонить
app.UseAuthentication();
в Startup.cs → Настроить метод до app.UseMvc();
Ответ 2
Вот полный рабочий минимум с контроллером. Надеюсь, вы сможете проверить это, используя Postman или JavaScript-вызов.
-
appsettings.json, appsettings.Development.json. Добавьте раздел. Примечание. Ключ должен быть довольно длинным, а эмитент - адресом услуги:
...
,"Tokens": {
"Key": "Rather_very_long_key",
"Issuer": "http://localhost:56268/"
}
...
!!! В реальном проекте не храните ключ в файле appsettings.json. Он должен храниться в переменной Environment и принимать его так:
Environment.GetEnvironmentVariable("JWT_KEY");
-
AuthRequest.cs: Dto сохраняет значения для прохождения логина и пароля:
public class AuthRequest
{
public string UserName { get; set; }
public string Password { get; set; }
}
-
Startup.cs в методе Configure() ПЕРЕД app.UseMvc():
app.UseAuthentication();
-
Startup.cs в ConfigureServices():
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
-
Добавить контроллер:
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly IConfiguration _config;
private readonly IUserManager _userManager;
public TokenController(IConfiguration configuration, IUserManager userManager)
{
_config = configuration;
_userManager = userManager;
}
[HttpPost("")]
[AllowAnonymous]
public IActionResult Login([FromBody] AuthRequest authUserRequest)
{
var user = _userManager.FindByEmail(model.UserName);
if (user != null)
{
var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
if (checkPwd)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_config["Tokens:Issuer"],
_config["Tokens:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
}
return BadRequest("Could not create token");
}}
Что все люди! Ура!
ОБНОВЛЕНИЕ: Люди спрашивают, как получить текущего пользователя. Todo:
-
В Startup.cs в ConfigureServices() добавьте
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
-
В контроллере добавьте конструктор:
private readonly int _currentUser;
public MyController(IHttpContextAccessor httpContextAccessor)
{
_currentUser = httpContextAccessor.CurrentUser();
}
-
Добавьте где-нибудь расширение и используйте его в своем контроллере (используя....)
public static class IHttpContextAccessorExtension
{
public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
{
var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
int.TryParse(stringId ?? "0", out int userId);
return userId;
}
}
Что все люди.
Ответ 3
Вот вам решение.
В вашем startup.cs, во-первых, настройте его как службы:
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = "somethong",
ValidAudience = "something",
:
};
});
второй, вызовите эти службы в config
app.UseAuthentication();
теперь вы можете использовать его в своем контроллере, добавив атрибут
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public IActionResult GetUserInfo()
{
Для получения полной информации о исходном коде, использующем angular в качестве Frond-end, см. здесь
Ответ 4
Вот моя реализация для API.Net Core 2.0:
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Add framework services
services.AddMvc(
config =>
{
// This enables the AuthorizeFilter on all endpoints
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
}
).AddJsonOptions(opt =>
{
opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
services.AddLogging();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = Configuration["AzureAD:Audience"];
options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
app.UseMvcWithDefaultRoute();
}
appsettings.json:
{
"AzureAD": {
"AADInstance": "https://login.microsoftonline.com/",
"Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Domain": "mydomain.com",
"TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
...
}
Вышеприведенный код позволяет использовать auth для всех контроллеров. Чтобы разрешить анонимный доступ, вы можете украсить весь контроллер:
[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
...
}
или просто украсить метод, чтобы разрешить одну конечную точку:
[AllowAnonymous]
[HttpPost("anonymousmethod")]
public async Task<IActionResult> MyAnonymousMethod()
{
...
}
Примечания:
- Это моя первая попытка аутентификации AD - если что-то не так, сообщите мне!
-
Audience
должен соответствовать запросу Идентификатор ресурса, запрошенному клиентом. В нашем случае наш клиент (веб-приложение Angular) был зарегистрирован отдельно в Azure AD, и он использовал свой идентификатор клиента, который мы зарегистрировали в качестве Аудитории в API
-
ClientId
называется Идентификатор приложения на портале Azure (почему?), идентификатор приложения для регистрации приложения для API.
-
TenantId
называется Идентификатор каталога на портале Azure (почему?), найденный под Azure Active Directory > Свойства
-
Если вы развертываете API в качестве веб-приложения, размещенного в Azure, убедитесь, что вы установили параметры приложения:
например. AzureAD: Аудитория /xxxxxxxx -xxxx-xxxx-xxxx-xxxxxxxxxxxx
Ответ 5
Asp.net Core 2.0 JWT Реализация аутентификации маркера несущей с использованием Web Api Demo
Добавить пакет " Microsoft.AspNetCore.Authentication.JwtBearer"
Startup.cs ConfigureServices()
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "me",
ValidAudience = "you",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
};
});
Startup.cs Настроить()
// ===== Use Authentication ======
app.UseAuthentication();
User.cs//Это пример класса модели. Это может быть что угодно.
public class User
{
public Int32 Id { get; set; }
public string Username { get; set; }
public string Country { get; set; }
public string Password { get; set; }
}
UserContext.cs//Это просто класс контекста. Это может быть что угодно.
public class UserContext : DbContext
{
public UserContext(DbContextOptions<UserContext> options) : base(options)
{
this.Database.EnsureCreated();
}
public DbSet<User> Users { get; set; }
}
AccountController.cs
[Route("[controller]")]
public class AccountController : Controller
{
private readonly UserContext _context;
public AccountController(UserContext context)
{
_context = context;
}
[AllowAnonymous]
[Route("api/token")]
[HttpPost]
public async Task<IActionResult> Token([FromBody]User user)
{
if (!ModelState.IsValid) return BadRequest("Token failed to generate");
var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
if (userIdentified == null)
{
return Unauthorized();
}
user = userIdentified;
//Add Claims
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
new Claim(JwtRegisteredClaimNames.Sub, "data"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("me",
"you",
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = DateTime.Now.AddMinutes(30),
token_type = "bearer"
});
}
}
UserController.cs
[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly UserContext _context;
public UserController(UserContext context)
{
_context = context;
if(_context.Users.Count() == 0 )
{
_context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
_context.SaveChanges();
}
}
[HttpGet("[action]")]
public IEnumerable<User> GetList()
{
return _context.Users.ToList();
}
[HttpGet("[action]/{id}", Name = "GetUser")]
public IActionResult GetById(long id)
{
var user = _context.Users.FirstOrDefault(u => u.Id == id);
if(user == null)
{
return NotFound();
}
return new ObjectResult(user);
}
[HttpPost("[action]")]
public IActionResult Create([FromBody] User user)
{
if(user == null)
{
return BadRequest();
}
_context.Users.Add(user);
_context.SaveChanges();
return CreatedAtRoute("GetUser", new { id = user.Id }, user);
}
[HttpPut("[action]/{id}")]
public IActionResult Update(long id, [FromBody] User user)
{
if (user == null)
{
return BadRequest();
}
var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
if (userIdentified == null)
{
return NotFound();
}
userIdentified.Country = user.Country;
userIdentified.Username = user.Username;
_context.Users.Update(userIdentified);
_context.SaveChanges();
return new NoContentResult();
}
[HttpDelete("[action]/{id}")]
public IActionResult Delete(long id)
{
var user = _context.Users.FirstOrDefault(u => u.Id == id);
if (user == null)
{
return NotFound();
}
_context.Users.Remove(user);
_context.SaveChanges();
return new NoContentResult();
}
}
Тест на PostMan:
![В ответ вы получите токен.]()
Передайте TokenType и AccessToken в заголовке в других веб-сервисах.
![введите описание изображения здесь]()
Лучшее из удачи! Я просто начинаю. Я потратил одну неделю, чтобы начать изучение ядра asp.net.