0

Я делаю небольшой сервис с использованием ASP.NET Core. Первой сложной задачей, с которой я столкнулся сейчас, является аутентификация пользователя в моей системе.Ошибка аутентификацииHandler AuthenticationScheme: Носитель был запрещен

Позвольте мне представить мой поток аутентификации:

+) Client ->вызова (апи/счет/авторизовать) ->система проверяет, является ли клиент или нет ->Отправить токен обратно к клиенту, так как он действителен.

+) Клиент ->использует полученные лексемы ->запросы к API/счет/фильтру ->службы проверяет маркер и забросить информацию обратно.

Я прочитал несколько руководств о JWT, но ответ не содержит достаточной информации, поскольку мне нужно. Я хочу:

  • Throw 401 и сообщение описывает, что код состояния, то есть: "ACCOUNT_DISABLED", "ACCOUNT_PENDING", "ACCOUNT_PERMISSION_INSUFFICIENT", ... не только 401.

Therfore, я реализовал свой собственный Authenticate валидатор:

public class BearerAuthenticationHandler : AuthenticationHandler<BearerAuthenticationOption> 
{ 
    #region Properties 

    /// <summary> 
    /// Inject dependency service into the handler. 
    /// </summary> 
    private readonly JwtTokenSetting _encryptionSetting; 

    /// <summary> 
    /// Inject dependency service into the handler. 
    /// </summary> 
    private readonly IEncryptionService _encryptionService; 

    /// <summary> 
    /// Inject time service to handler. 
    /// </summary> 
    private readonly ITimeService _timeService; 

    private readonly IRepositoryAccount _repositoryAccount; 

    #endregion 

    #region Constructors 

    /// <summary> 
    /// Initialize an instance of handler with specific dependency injections. 
    /// </summary> 
    /// <param name="encryptionSetting"></param> 
    /// <param name="encryptionService"></param> 
    /// <param name="timeService"></param> 
    /// <param name="repositoryAccount"></param> 
    public BearerAuthenticationHandler(JwtTokenSetting encryptionSetting, IEncryptionService encryptionService, ITimeService timeService, IRepositoryAccount repositoryAccount) 
    { 
     _encryptionSetting = encryptionSetting; 
     _encryptionService = encryptionService; 
     _timeService = timeService; 
     _repositoryAccount = repositoryAccount; 
    } 

    #endregion 

    #region Methods 

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync() 
    { 
     #region Token analyzation 

     // Find the authorization key in request. 
     var authorizationKey = 
      Request.Headers.Keys.FirstOrDefault(x => x.Equals("authorization", StringComparison.OrdinalIgnoreCase)); 

     // Authorization key is not found in the request. 
     if (string.IsNullOrWhiteSpace(authorizationKey)) 
      return AuthenticateResult.Fail("No authorization is found in request header."); 

     // Find the token in Authorization. 
     var authorizationValue = Request.Headers[authorizationKey].ToString(); 

     // Authentication scheme prefix. 
     var authenticationScheme = $"{Options.AuthenticationScheme} "; 

     // No token has been specified. 
     if (string.IsNullOrWhiteSpace(authorizationValue) || !authorizationValue.StartsWith(authenticationScheme, StringComparison.OrdinalIgnoreCase)) 
      return AuthenticateResult.Fail("No bearer token is found in request header."); 

     // Cut the string to obtain bearer token. 
     var accessToken = authorizationValue.Substring(authenticationScheme.Length); 

     #endregion 

     #region Token validation 

     // Decrypt the token. 
     var tokenDetailViewModel = _encryptionService.Decrypt<TokenDetailViewModel>(accessToken, _encryptionSetting.Key); 

     // No detail has been found. 
     if (tokenDetailViewModel == null) 
     { 
      InitializeHttpResponse(Response, HttpStatusCode.Unauthorized, new HttpResponseViewModel 
      { 
       Message = "TOKEN_INVALID" 
      }); 

      return AuthenticateResult.Fail("Token is invalid"); 
     } 

     // Find the current unix time on server. 
     var unixTime = _timeService.UtcToUnix(DateTime.UtcNow); 

     // Token is expired. 
     if (unixTime > tokenDetailViewModel.Expire) 
     { 
      InitializeHttpResponse(Response, HttpStatusCode.Unauthorized, new HttpResponseViewModel 
      { 
       Message = "TOKEN_EXPIRED" 
      }); 

      return AuthenticateResult.Fail("Token is expired"); 
     } 

     // Account filter construction. 
     var filterAccountViewModel = new FilterAccountViewModel 
     { 
      Email = tokenDetailViewModel.Email, 
      EmailComparison = TextComparision.Equal, 
      Password = tokenDetailViewModel.Password, 
      PasswordComparision = TextComparision.EqualIgnoreCase, 
      Statuses = new[] { AccountStatus.Active } 
     }; 

     // Find the first condition statisfied account in the database. 
     var account = await _repositoryAccount.FindAccountAsync(filterAccountViewModel); 

     // Account cannot be found in the database. 
     if (account == null) 
     { 
      InitializeHttpResponse(Response, HttpStatusCode.Unauthorized, new HttpResponseViewModel 
      { 
       Message = "ACCOUNT_INVALID" 
      }); 
      return AuthenticateResult.Fail("Account is invalid"); 
     } 

     #endregion 

     var claimsIdentity = new ClaimsIdentity(); 
     claimsIdentity.AddClaim(new Claim(nameof(JwtClaim.Email), account.Email)); 
     claimsIdentity.AddClaim(new Claim(nameof(JwtClaim.Status), nameof(account.Status))); 

     // Update user into context. 
     var claimPrincipal = new ClaimsPrincipal(claimsIdentity); 

     // Initialize an authentication ticket. 
     var authenticationTicket = new AuthenticationTicket(claimPrincipal, new AuthenticationProperties 
     { 
      AllowRefresh = true, 
      ExpiresUtc = DateTime.UtcNow.AddMinutes(30), 
      IsPersistent = true, 
      IssuedUtc = DateTime.UtcNow 
     }, "Bearer"); 

     return AuthenticateResult.Success(authenticationTicket); 
    } 

    /// <summary> 
    /// Initialize an application/json response. 
    /// </summary> 
    /// <param name="httpResponse"></param> 
    /// <param name="httpStatusCode"></param> 
    /// <param name="httpResponseViewModel"></param> 
    private void InitializeHttpResponse(HttpResponse httpResponse, HttpStatusCode httpStatusCode, HttpResponseViewModel httpResponseViewModel) 
    { 
     // Response must be always application/json. 
     httpResponse.ContentType = "application/json"; 
     httpResponse.StatusCode = (int)httpStatusCode; 

     if (httpResponseViewModel == null) 
      return; 

     using (var streamWriter = new StreamWriter(httpResponse.Body)) 
     { 
      streamWriter.AutoFlush = true; 
      streamWriter.WriteLineAsync(JsonConvert.SerializeObject(httpResponseViewModel)); 
     } 
    } 

    #endregion 
} 

Вот мой AccountController:

[Route("api/[controller]")] 
public class AccountController : Controller 
{ 
    private readonly IRepositoryAccount _repositoryAccount; 

    private readonly IEncryptionService _encryptionService; 

    private readonly ITimeService _timeService; 

    private readonly JwtTokenSetting _jwtTokenSetting; 

    public AccountController(IRepositoryAccount repositoryAccount, IEncryptionService encryptionService, ITimeService timeService, 
     IOptions<JwtTokenSetting> jwtTokenSetting) 
    { 
     _repositoryAccount = repositoryAccount; 
     _encryptionService = encryptionService; 
     _timeService = timeService; 
     _jwtTokenSetting = jwtTokenSetting.Value; 
    } 

    [HttpPost("authorize")] 
    [AllowAnonymous] 
    public async Task<IActionResult> Authorize([FromBody] LoginViewModel loginViewModel) 
    { 
     // Find the encrypted password of login information. 
     var filterAccountViewModel = new FilterAccountViewModel(); 
     filterAccountViewModel.Email = loginViewModel.Email; 
     filterAccountViewModel.EmailComparison = TextComparision.Equal; 
     filterAccountViewModel.Password = _encryptionService.FindEncryptPassword(loginViewModel.Password); 
     filterAccountViewModel.PasswordComparision = TextComparision.EqualIgnoreCase; 
     filterAccountViewModel.Statuses = new[] {AccountStatus.Active}; 

     // Initialize HttpResponseViewModel. 
     var httpResponseViewModel = new HttpResponseViewModel(); 

     // Find the account. 
     var account = await _repositoryAccount.FindAccountAsync(filterAccountViewModel); 

     // Account is not found. 
     if (account == null) 
     { 
      Response.ContentType = "application/json"; 
      using (var streamWriter = new StreamWriter(Response.Body)) 
      { 
       httpResponseViewModel.Message = "ACCOUNT_INVALID"; 
       await streamWriter.WriteLineAsync(JsonConvert.SerializeObject(httpResponseViewModel)); 
      } 

      return new UnauthorizedResult(); 
     } 

     // Initialize token detail. 
     var tokenDetailViewModel = new TokenDetailViewModel 
     { 
      Email = loginViewModel.Email, 
      Password = filterAccountViewModel.Password, 
      Expire = _timeService.UtcToUnix(DateTime.UtcNow.AddSeconds(_jwtTokenSetting.Expire)) 
     }; 

     // Initialize token information and throw to client for their future use. 
     var tokenGeneralViewModel = new TokenGeneralViewModel 
     { 
      AccessToken = _encryptionService.Encrypt(tokenDetailViewModel, _jwtTokenSetting.Key), 
      Expire = _jwtTokenSetting.Expire 
     }; 

     return Ok(tokenGeneralViewModel); 
    } 


    [HttpPost("filter")] 
    [Authorize(ActiveAuthenticationSchemes = "Bearer")] 
    public IEnumerable<string> FindAllAccounts() 
    { 
     Response.StatusCode = (int)HttpStatusCode.Accepted; 
     return new[] { "1", "2", "3", "4" }; 
    } 
} 

Когда я использую маркер, порожденную API/счета/разрешить доступ к API/счета/фильтр. Ошибка была брошена мне:

AuthenticationScheme: Знаменосец было запрещено

Может кто-нибудь пожалуйста, скажите мне, почему? Является ли моя реализация лучшим подходом или нет?

Спасибо,

ответ

1

Является ли моя реализация лучший подход или нет?

Я бы не сделал этого, как вы внедрили. Потому что (1 и 3 являются только мое мнение)

  1. ACCOUNT_DISABLED, ACCOUNT_PENDING, ACCOUNT_PERMISSION_INSUFFICIENT это статусы не означает, что пользователь должен перепечатывать свои cridentials.
  2. Даже если я хочу использовать 401 с сообщением, прежде чем создать свою собственную реализацию обработчика , я бы рассмотрел возможность использования событий переноса jwt.OnChallenge Событие кажется хорошим для этого (см. Это answer как реализовать).
  3. Я думаю, что ваше требование связано с авторизацией, а не с аутентификацией. Поэтому написать политику было бы лучше.

Чтобы использовать политику я не знаю простую реализацию, но вот моя попытка:

Авторизация Handler:

public class CheckUserRequirement : IAuthorizationRequirement 
{ 
} 
public class CheckUserAuthorizationHandler : AuthorizationHandler<CheckUserRequirement> 
{ 
    private readonly IHttpContextAccessor _accessor; 
    public SimpleAuthorizationHandler(IHttpContextAccessor accessor) 
    { 
     _accessor = accessor; 
    } 
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SimpleRequirement requirement) 
    { 
     if(account.isDisabled) 
     { 
      _accessor.HttpContext.Response.Headers.Add("error_code", "ACCOUNT_DISABLED"); 
     } 
     //... 
     context.Succeed(requirement); 
    } 
} 

ConfigureServices:

 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 
     services.AddScoped<IAuthorizationHandler, CheckUserAuthorizationHandler>(); 
     services.AddAuthorization(options => 
     { 
      options.AddPolicy("CheckUser", policy => { policy.AddRequirements(new CheckUserRequirement()); }); 
     }); 

И использовать его:

[Authorize(Policy = "CheckUser")] 
public class SomeController 

Редактировать

Я предложил OnChallenge событие, но я понял, что он не подходит для вашего случая. См. Мой другой answer

+0

Получил его. Благодарим за помощь: D – Redplane

Смежные вопросы