AuthenticationHandler error AuthenticationScheme: Bearer was



我正在用ASP做一个小服务。净的核心。我现在面临的第一件复杂的事情是认证一个用户进入我的系统。

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

+)Client -> call (api/account/authorize) -> 系统检查客户端是否有效 -> 当客户端有效时发送token回给客户端

+) 客户端 -> 使用获得的令牌 -> 请求api/account/filter -> 服务验证令牌并抛出信息

我已经阅读了一些关于JWT的教程,但是响应中没有包含我需要的足够的信息。我想要:

  • 抛出401一条消息描述状态码,即:"ACCOUNT_DISABLED", "ACCOUNT_PENDING", " account_permission_sufficient",…不只是401.
因此,我实现了我自己的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/account/authorize生成的令牌访问api/account/filter时。错误提示:

AuthenticationScheme: Bearer was forbidden

有谁能告诉我为什么吗?我的实现是不是最好的方法?

谢谢你,

我的实现是不是最好的方法?

我不会像你实现的那样做。因为(1和3只是我的意见)

  1. ACCOUNT_DISABLED, ACCOUNT_PENDINGACCOUNT_PERMISSION_INSUFFICIENT这个状态并不意味着用户必须重新输入它的证书。
  2. 即使我想在创建自己的消息之前使用401处理程序实现时,我会考虑使用JWT承载事件。OnChallenge事件似乎很好这样做(参见这个答案如何实现)。
  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事件,但我意识到它不适合你的情况。参见我的另一个答案

相关内容

  • 没有找到相关文章

最新更新