代码之家  ›  专栏  ›  技术社区  ›  Redplane

AuthenticationHandler错误AuthenticationScheme:禁止承载

  •  0
  • Redplane  · 技术社区  · 9 年前

    我正在用ASP做一个小服务。NET核心。我现在面临的第一件复杂的事情是对系统中的用户进行身份验证。

    让我介绍一下我的身份验证流程:

    客户 -> 调用(api/帐户/授权) -> 系统检查客户端是否有效 -> 将令牌发回客户,因为他/她是有效的 .

    +) 客户 -> 使用获取的令牌 -> 对api/account/filter的请求 -> 服务验证令牌并返回信息 .

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

    • 扔401 一条消息描述该状态代码 ,即:“ 帐户_禁用 ", " 帐户_结束 ", " 账户_期限_不足 “,……不仅仅是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/aaccount/filter时。向我抛出了一个错误:

    AuthenticationScheme:禁止持有者

    非常感谢。

    1 回复  |  直到 9 年前
        1
  •  2
  •   Community Mohan Dere    9 年前

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

    1. ACCOUNT_DISABLED , ACCOUNT_PENDING , ACCOUNT_PERMISSION_INSUFFICIENT 这种状态并不意味着用户必须 重新键入其凭证。
    2. 处理程序实现,我会考虑使用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);
        }
    }  
    

    配置服务:

            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 
    

    编辑

    我建议过 answer