/// <summary>
        /// Validates the specified request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage"/> to validate</param>
        /// <param name="resolver">An account name to shared secret resolver.</param>
        /// <param name="maxAge">The maximum age of a message that will be accepted</param>
        /// <returns>A ClaimsPrincipal for the the identity which owns the message.</returns>
        public static ClaimsPrincipal Validate(HttpRequestMessage request, Func <string, byte[]> resolver, TimeSpan maxAge)
        {
            AuthenticationHeaderValue authenticationHeader = null;

            if (request.Headers != null)
            {
                authenticationHeader = request.Headers.Authorization;
            }

            string accountName;

            byte[] sentHmac;

            if (request.Headers != null && DateTime.Now - request.Headers.Date > maxAge)
            {
                throw new ForbiddenException("Request expired.");
            }

            if (authenticationHeader == null ||
                string.IsNullOrWhiteSpace(authenticationHeader.Parameter) ||
                authenticationHeader.Scheme != SharedKeyAuthentication.Scheme ||
                !SharedKeyAuthentication.TryParse(authenticationHeader.Parameter, out accountName, out sentHmac))
            {
                return(AnonymousPrincipal);
            }

            byte[] sharedKey = resolver(accountName);

            if (sharedKey == null || sharedKey.Length == 0)
            {
                // Account not found
                throw new UnauthorizedException();
            }

            if (request.Headers.TransferEncodingChunked == null || !(bool)request.Headers.TransferEncodingChunked)
            {
                if (request.Content.Headers.ContentLength != null && (long)request.Content.Headers.ContentLength > 0)
                {
                    var sentHash = request.Content.Headers.ContentMD5;

                    if (sentHash == null)
                    {
                        throw new ForbiddenException(
                                  "Content-MD5 header must be specified when a request body is included.");
                    }

                    if (!CompareHash(sentHash, SignatureValidator.CalculateBodyMd5(request).Result))
                    {
                        throw new PreconditionFailedException("Content-MD5 does not match the request body.");
                    }
                }
            }

            // Now check the actual auth signature.
            byte[] calculatedHmac = SharedKeySignature.Calculate(request, accountName, sharedKey);

            if (!CompareHash(sentHmac, calculatedHmac))
            {
                throw new ForbiddenException("Token validation failed.");
            }

            var claims = new List <Claim> {
                new Claim(ClaimTypes.Name, accountName)
            };
            var claimsIdentity  = new ClaimsIdentity(claims, SharedKeyAuthentication.Scheme);
            var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

            return(claimsPrincipal);
        }