ASP. NET Core 3.1 - Как сохранить токены JWT после аутентификации - PullRequest
0 голосов
/ 30 марта 2020

Я пытался заставить JWT Authentication работать, и не совсем понятно, как это нужно сделать, и как лучше всего это сделать в ASP. NET Core 3.1.

Я использовал аутентификацию на основе Cook ie, которая, как я полагаю, связана с идентификатором сеанса, который связан с работающим экземпляром сервера. Если я хочу использовать несколько серверов с разными IP-адресами и портами, я предполагаю, что файлы cookie больше не будут работать, и для этого потребуется что-то еще, что может быть проверено в разных системах.

Я следил за различными примерами в Интернете, но не ясно, что делать после того, как у меня есть токен JWT после того, как пользователь прошел «Аутентификацию» - вход в систему. После входа в систему пользователи могут получить доступ к любой части системы через: html ссылки ( меню ).

Как мне пройти токены со всеми последующими запросами?

Перенаправить ли я пользователя на страницу приветствия после аутентификации пользователя и сохранить токен в браузере sessionStore или localStorage или Cook ie? Как лучше всего с этим справиться.

options.success = function (obj) {
     sessionStorage.setItem("token", obj.token);
     sessionStorage.setItem("userName",$("#userName").val());
}

HTTP HEADERS

Будет ли работать переменная Authorization HTTP Header и будет ли это отправляться во всех последующих запросах браузером, действующим как HTTP-клиент. Как долго длится этот заголовок HTTP, теряется ли он после закрытия сокета TCP? Как мне установить эту переменную заголовка HTTP в ASP. NET Core 3.1? Будет ли сервер затем использовать этот заголовок для проверки токена, а также передать его снова для использования в последующих запросах?

В настоящее время у меня есть это, которое возвращает токен в теле после проверки подлинности пользователя :

        var claims = await GetClaims(user);
        var token = GenerateSecurityToken(claims);

        return Ok(new { Token = token })

AJAX CALLS

У меня есть несколько форм и несколько вызовов AJAX, как реализовать это как ручной подход кажется довольно утомительным.

Есть ли способ получить токен JWT из скрытой переменной формы, аналогичной токену AntiForgery @Html.AntiForgeryToken(), который используется во всех моих Ajax вызовах?

jQuery с использованием скрытой переменной формы:

request = $.ajax({
    async: true,
    url: url,
    type: "POST",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    headers: {
        RequestVerificationToken:
        $('input:hidden[name="__RequestVerificationToken"]').val()
    },
    WHAT DO I ADD FOR JWT ? 
    data: JSON.stringify(data)
}).done(function() {
    completion();
}).fail(function() {
    // fail
});

HTML FORMS

У меня есть Razor Pages и некоторые формы, которые затем отправляются обратно в контроллеры. Как включить токен?

КОНТРОЛЛЕРЫ

Есть ли что-то еще, что нужно выполнить при использовании JWT, помимо того, что есть в моем Startup.cs? Я знаю, что мне нужно иметь дело с обновлениями Token, но я уйду к отдельному вопросу.

ССЫЛКИ ИЗ МЕНЮ - HTTP GET

Я мог манипулировать меню / ссылками, представленными пользователю, добавляя токен в конец URL, но как это должно быть сделано?

1 Ответ

0 голосов
/ 01 апреля 2020

После долгого чтения я нашел несколько ответов вместе с рабочим решением.

HTTP HEADERS Как только у вас есть токен, токен должен быть сохранен, чтобы получить доступ. в систему. Использование HTTP-заголовков для хранения токена не будет сохраняться, так как HTTP-протоколы 1.0, 1.1 и 1.2 закроют сокет TCP в какой-то момент вместе с состоянием, которое он имел, токен. Не идеально подходит для веб-клиентов, где вы не управляете соединениями Http, но могли бы использоваться для разработки на мобильных устройствах, Android или IOS, где вы можете управлять HttpHeaders.

МЕСТНОЕ ХРАНЕНИЕ Вы можете используйте браузеры localStorage или sessionStorage, но они имеют некоторые риски безопасности, когда JavaScript может прочитать значения - XSS атака.

COOKIES Другой вариант - сохранить токен в Cookie; Повар ie будет передаваться вместе с каждым запросом http, и на стороне клиента ничего особенного не должно произойти в связи с этим. Этот метод не подвержен атакам XSS. Но склонен к CSRF. Но опять же CORS может помочь с этим.

Также лучше установить Cook ie как HttpOnly, таким образом, Cook ie будет доставляться только через HTTPS. Подробнее здесь

Вот моя реализация, основанная на статье, которую я нашел здесь

Startup.cs ConfigureServices .. .

        // openssl rand -hex 16 => 32 bytes when read
        var jwt_key = Configuration.GetSection("JwtOption:IssuerSigningKey").Value;
        var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwt_key));
        var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,

                // Validate the JWT Issuer (iss) claim
                ValidateIssuer = true,
                ValidIssuer = "some uri",

                // Validate the JWT Audience (aud) claim
                ValidateAudience = true,
                ValidAudience = "the web",

                // Validate the token expiry
                ValidateLifetime = true,

                // If you want to allow a certain amount of clock drift, set that here:
                ClockSkew = TimeSpan.Zero
            };

        services.AddSingleton(tokenValidationParameters);

        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
        {
            int minute = 60;
            int hour = minute * 60;
            int day = hour * 24;
            int week = day * 7;
            int year = 365 * day;

            options.LoginPath = "/auth/login";
            options.AccessDeniedPath = "/auth/accessdenied";
            options.Cookie.IsEssential = true;
            options.SlidingExpiration = true;
            options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2);

            options.Cookie.Name = "access_token";

            options.TicketDataFormat = new CustomJwtDataFormat(
                SecurityAlgorithms.HmacSha256,
                tokenValidationParameters);
        });

CustomJwtDataFormat Это будет проверка наших токенов.

public class CustomJwtDataFormat :ISecureDataFormat<AuthenticationTicket>
{
    private readonly string algorithm;
    private readonly TokenValidationParameters validationParameters;

    public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
    {
        this.algorithm = algorithm;
        this.validationParameters = validationParameters;
    }

    public AuthenticationTicket Unprotect(string protectedText)
        => Unprotect(protectedText, null);

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var handler = new JwtSecurityTokenHandler();
        ClaimsPrincipal principal = null;
        SecurityToken validToken = null;

        try
        {
            principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);

            var validJwt = validToken as JwtSecurityToken;

            if (validJwt == null)
            {
                throw new ArgumentException("Invalid JWT");
            }

            if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
            {
                throw new ArgumentException($"Algorithm must be '{algorithm}'");
            }

            // Additional custom validation of JWT claims here (if any)
        }
        catch (SecurityTokenValidationException e)
        {
            System.Console.WriteLine(e);
            return null;
        }
        catch (ArgumentException e)
        {
            System.Console.WriteLine(e);
            return null;
        }

        // Validation passed. Return a valid AuthenticationTicket:
        return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");
    }

    // This ISecureDataFormat implementation is decode-only
    public string Protect(AuthenticationTicket data)
    {
        throw new NotImplementedException();
    }

    public string Protect(AuthenticationTicket data, string purpose)
    {
        throw new NotImplementedException();
    }
}

LoginController После проверки имени пользователя и пароля вызовите SignInUser

    private string GenerateSecurityToken(List<Claim> claims)
    {  
        var tokenHandler = new JwtSecurityTokenHandler();
        var expire = System.DateTime.UtcNow.AddMinutes(userService.GetJwtExpireDate());
        var tokenDescriptor = new SecurityTokenDescriptor
        {  
            Subject = new ClaimsIdentity(claims),
            Expires = expire,
            SigningCredentials = new SigningCredentials(tokenValidationParameters.IssuerSigningKey, SecurityAlgorithms.HmacSha256Signature),
            Audience = tokenValidationParameters.ValidAudience,
            Issuer = tokenValidationParameters.ValidIssuer
        };  

        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }

    private async Task<List<Claim>> GetClaims(UserModel user) {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Email),
            new Claim(ClaimTypes.Email, user.Email),
        };

        // add roles
        var roleList = await userService.UserRoles(user.Email);
        foreach (var role in roleList)
        {
            var claim = new Claim(ClaimTypes.Role, role.Role);
            claims.Add(claim);
        }

        return claims;
    }

    private async Task<IActionResult> SignInUser(UserModel user, bool rememberMe)
    {
        var claims = await GetClaims(user);
        var token = GenerateSecurityToken(claims);

        // return Ok(new { Token = token });
        // HttpContext.Request.Headers.Add("Authorization", $"Bearer {token}");

        // HttpContext.Response.Cookies.Append(
        HttpContext.Response.Cookies.Append("access_token", token, new CookieOptions { HttpOnly = true, Secure = true }); 
        return RedirectToAction("Index", "Home", new { area = "" });
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...