Подтвердить что ты не робот

JWT и веб-API (JwtAuthForWebAPI?) - поиск примера

У меня есть проект веб-API, выделенный с помощью Angular, и я хочу его защитить, используя токен JWT. У меня уже есть проверка подлинности пользователя/пароля, поэтому я думаю, что мне просто нужно реализовать часть JWT.

Я полагаю, что я остановился на JwtAuthForWebAPI, поэтому пример использования этого будет замечательным.

Я предполагаю, что любой метод, не украшенный [Authorize], будет вести себя так, как он есть всегда, и что любой метод, украшенный [Authorize], будет 401, если токен, переданный клиентом, не совпадает.

Что я еще не могу понять, как отправить маркер обратно клиенту при первоначальной аутентификации.

Я пытаюсь использовать магическую строку для начала, поэтому у меня есть этот код:

RegisterRoutes(GlobalConfiguration.Configuration.Routes);
var builder = new SecurityTokenBuilder();
var jwtHandler = new JwtAuthenticationMessageHandler
{
    AllowedAudience = "http://xxxx.com",
    Issuer = "corp",
    SigningToken = builder.CreateFromKey(Convert.ToBase64String(new byte[]{4,2,2,6}))
};

GlobalConfiguration.Configuration.MessageHandlers.Add(jwtHandler);

Но я не уверен, как это сначала возвращается к клиенту. Я думаю, что я понимаю, как обрабатывать это на клиенте, но бонусные баллы, если вы также можете показать сторону Angular этого взаимодействия.

4b9b3361

Ответ 1

Мне пришлось взять информацию из нескольких разных мест, чтобы создать решение, которое работает для меня (в действительности, начало производственного жизнеспособного решения, но оно работает!)

Я избавился от JwtAuthForWebAPI (хотя я взял одну часть из него, чтобы разрешить запросы без заголовка авторизации для прохождения через методы WebAPI Controller, не защищенные [Authorize]).

Вместо этого я использую библиотеку Microsoft JWT (JSON Web Token Handler для Microsoft.NET Framework - от NuGet).

В моем методе проверки подлинности после выполнения фактической проверки подлинности я создаю строковую версию токена и передаю его вместе с аутентифицированным именем (то же самое имя пользователя, переданное мне в этом случае) и роль, которая на самом деле, вероятно, будет получен во время аутентификации.

Здесь метод:

[HttpPost]
public LoginResult PostSignIn([FromBody] Credentials credentials)
{
    var auth = new LoginResult() { Authenticated = false };

    if (TryLogon(credentials.UserName, credentials.Password))
    {
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, credentials.UserName), 
                new Claim(ClaimTypes.Role, "Admin")
            }),

            AppliesToAddress = ConfigurationManager.AppSettings["JwtAllowedAudience"],
            TokenIssuerName = ConfigurationManager.AppSettings["JwtValidIssuer"],
            SigningCredentials = new SigningCredentials(new 
                InMemorySymmetricSecurityKey(JwtTokenValidationHandler.SymmetricKey),
                "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
                "http://www.w3.org/2001/04/xmlenc#sha256")
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);

            auth.Token = tokenString;
            auth.Authenticated = true;
        }

    return auth;
}

UPDATE

Возникла проблема с обработкой маркера при последующих запросах. То, что я сделал, это создать DelegatingHandler, чтобы попытаться прочитать/декодировать токен, затем создать Принципала и установить его в Thread.CurrentPrincipal и HttpContext.Current.User(вам нужно установить его в оба). Наконец, я украшаю методы контроллера соответствующими ограничениями доступа.

Здесь мясо ДелегированияHandler:

private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
    token = null;
    IEnumerable<string> authzHeaders;
    if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
    {
        return false;
    }
    var bearerToken = authzHeaders.ElementAt(0);
    token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
    return true;
}


protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    HttpStatusCode statusCode;
    string token;

    var authHeader = request.Headers.Authorization;
    if (authHeader == null)
    {
        // missing authorization header
        return base.SendAsync(request, cancellationToken);
    }

    if (!TryRetrieveToken(request, out token))
    {
        statusCode = HttpStatusCode.Unauthorized;
        return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
    }

    try
    {
        JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
        TokenValidationParameters validationParameters =
            new TokenValidationParameters()
            {
                AllowedAudience = ConfigurationManager.AppSettings["JwtAllowedAudience"],
                ValidIssuer = ConfigurationManager.AppSettings["JwtValidIssuer"],
                SigningToken = new BinarySecretSecurityToken(SymmetricKey)
            };

        IPrincipal principal = tokenHandler.ValidateToken(token, validationParameters);
        Thread.CurrentPrincipal = principal;
        HttpContext.Current.User = principal;

        return base.SendAsync(request, cancellationToken);
    }
    catch (SecurityTokenValidationException e)
    {
        statusCode = HttpStatusCode.Unauthorized;
    }
    catch (Exception)
    {
        statusCode = HttpStatusCode.InternalServerError;
    }

    return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
}

Не забудьте добавить его в конвейер MessageHandlers:

public static void Start()
{
    GlobalConfiguration.Configuration.MessageHandlers.Add(new JwtTokenValidationHandler());
}

Наконец, украсьте свои методы контроллера:

[Authorize(Roles = "OneRoleHere")]
[GET("/api/admin/settings/product/allorgs")]
[HttpGet]
public List<Org> GetAllOrganizations()
{
    return QueryableDependencies.GetMergedOrganizations().ToList();
}

[Authorize(Roles = "ADifferentRoleHere")]
[GET("/api/admin/settings/product/allorgswithapproval")]
[HttpGet]
public List<ApprovableOrg> GetAllOrganizationsWithApproval()
{
    return QueryableDependencies.GetMergedOrganizationsWithApproval().ToList();
}