0

我正在使用ASP.NET Core进行一项小型服务。我现在面临的第一个复杂的事情是将用户身份验证到我的系统中。AuthenticationHandler错误AuthenticationScheme:承载被禁止

让我介绍一下我的认证流程:

+)客户 - >调用(API /帐号/授权) - >系统检查客户是否有效与否 - >发送令牌回到客户处,因为他/她有效

+)客户 - >使用所获得的令牌 - >请求API /帐户/过滤 - >服务验证令牌和抛回信息

我读过一些关于JWT的教程,但是回应没有包含足够的信息,因为我需要。 我想

  • 掷401消息描述了状态码,即: “ACCOUNT_DISABLED”, “ACCOUNT_PENDING”, “ACCOUNT_PERMISSION_INSUFFICIENT”,......不只是401.

因此,我实现了我自己的验证验证器:

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_DISABLEDACCOUNT_PENDINGACCOUNT_PERMISSION_INSUFFICIENT这种状态并不意味着用户必须重新输入 其cridentials。
  2. 即使我想用401消息,在创建我自己的 处理程序实现之前,我会考虑使用jwt载体事件。OnChallenge事件似乎很好做到这一点(见answer如何实现)。
  3. 我认为你的要求与授权而不是认证有关。所以写一个政策 会更好。

要使用的政策,我不知道简单的实现,但这里是我的尝试:

授权处理:

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