ASP.NET 核心应用程序 http 客户端请求到 Web api

ASP.NET Core application http client request to a web api

我有一个 运行 Web API,我试图从中取回不记名令牌。从 Postman 开始请求正在运行,我取回了令牌。从我的应用程序执行后,我总是收到 http 400 Bad Request 错误。

我在这里错过了什么?

public async Task<string> GetToken(string userName, string passWord)
{
    var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/login");

    request.Headers.Authorization = new AuthenticationHeaderValue(
                "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ userName}:{ passWord}")));
    request.Headers.Host = "api.my-host.com";
    request.Headers.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
            
    var response = await _httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode();

    using var responseStream = await response.Content.ReadAsStreamAsync();

    var authResult = await JsonSerializer.DeserializeAsync<AuthResult>(responseStream);

    return authResult == null ? "" : authResult.Access_Token;
}

根据要求,这是 Postman 结果的屏幕截图:

我创建了一个 HttpGet 请求并在代码中添加了来自 Postman 的不记名令牌,然后我收到了数据。只是令牌请求似乎有问题。

我的控制器:

namespace AmsAPI.Controller
{

    [Produces("application/json")]
    [Route("api/auth")]
    [ApiController]
    [Authorize]
    public class AuthenticationController : ControllerBase
    {
        private readonly IAuthenticationManager _authenticationManager;

        public AuthenticationController(IAuthenticationManager authenticationManager)
        {
            _authenticationManager = authenticationManager;
        }

        [HttpPost("login"), AllowAnonymous]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesDefaultResponseType]
        public async Task<ActionResult> Login([FromHeader] byte[] basic)
        {
            if (!ModelState.IsValid) return BadRequest();

            string Basic = Encoding.UTF8.GetString(basic);

            var splitBasic = Basic.Split(':');

            AuthCredentials credentials = new()
            {
                UserName = splitBasic[0],
                Password = splitBasic[1]
            };

            return await _authenticationManager.SignInCheck(credentials) ?
                Ok(new
                {
                    message = string.Format("User {0} successfully logged in.", credentials.UserName),
                    access_token = await _authenticationManager.CreateToken(),
                    token_type = "bearer",
                    expires_in = "3600"
                }) :
                Unauthorized();
        }

        [HttpGet("user")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesDefaultResponseType]
        public async Task<List<User>> GetUser() => await _authenticationManager.GetUser();
    }
}

好的,我已经成功重现了你的问题。

You are getting 400 because its searching for SSL credentials but by default we don't have certificate bind with our request and its not required. So to handle this 400 exception you have to use HttpClientHandler which will bypass certificate error. So you can try below way to get the token response successfully.

HTTP Request:

public async Task<string> GetToken()
        {

            var user = new UserCred();
            user.user_name = "admin";
            user.password = "123456";
            var handler = new HttpClientHandler();
            handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };

            var client = new HttpClient(handler);
            var json = JsonConvert.SerializeObject(user);

            var data = new StringContent(json, Encoding.UTF8, "application/json");
            var url = "http://localhost:21331/api/Authentication/login";
            var response = await client.PostAsync(url, data);

            string result = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine(result);
            return result;
            
        }

Output:

Note: You need to add below code snippet to resolve your issue. But I like to call HTTP POST request in above ways to make it little clean. You can continue with your code just adjust what I suggest.

        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
        var client = new HttpClient(handler);

希望以上步骤能相应地解决您的问题。

Postman 似乎正在发送基本授权 Header 作为 Http 请求的内容,而不是在 Header 中,但我的 Web 应用程序在 Header 中正确实施了授权]. 所以在 WebApi 中它看起来像

    [Authorize]
    [Route("api/auth")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
        private readonly IAuthenticationManager _authenticationManager;

        public AuthenticationController(IAuthenticationManager authenticationManager)
        {
            _authenticationManager = authenticationManager;
        }

        [HttpPost("login"), AllowAnonymous]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesDefaultResponseType]
        public async Task<ActionResult> Login()
        {
            //check if header has Authorization
            if (!Request.Headers.ContainsKey("Authorization")) return BadRequest();

            try
            {
                AuthenticationHeaderValue authenticationHeaderValue = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);

                if (authenticationHeaderValue == null) throw new Exception();

                var bytes = Convert.FromBase64String(authenticationHeaderValue.Parameter ?? throw new Exception());

                string[] creds = Encoding.UTF8.GetString(bytes).Split(':');

                AuthCredentials credentials = new()
                {
                    UserName = creds[0],
                    Password = creds[1]
                };

                return await _authenticationManager.SignInCheck(credentials) ?
                    Ok(new
                    {
                        message = string.Format("User {0} successfully logged in.", credentials.UserName),
                        access_token = await _authenticationManager.CreateToken(),
                        token_type = "bearer",
                        expires_in = "3600"
                    }) :
                    Unauthorized();
            }
            catch (Exception)
            {
                return BadRequest();
            }
        }
    }

我的要求如下:

 public async Task<string> GetToken(string userName, string passWord)
    {
        //set request
        var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/login");
        //set Header
        request.Headers.Authorization = new AuthenticationHeaderValue(
            "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ userName}:{ passWord}")));
        //get response
        var response = await _httpClient.SendAsync(request);

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();

        var authResult = await JsonSerializer.DeserializeAsync<AuthResult>(responseStream);

        var token = authResult.Access_Token;

        return authResult == null ? "Authorization failed!" : "Bearer token successfully created!";
    }

并且 httpClient 被外包到服务中

public static void ConfigureServices(this IServiceCollection services)
    {
        var url = Environment.GetEnvironmentVariable("amsApiUrl");
        var host = Environment.GetEnvironmentVariable("amsHostUrl");
        //set HttpClient
        services.AddHttpClient<IAmsAccountService, AmsAccountService>(c =>
        {
            c.BaseAddress = new Uri(url ?? "");
            c.DefaultRequestHeaders.Accept.Clear();
            c.DefaultRequestHeaders.Host = host;
            c.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
            c.Timeout = TimeSpan.FromSeconds(10);
        })
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                CookieContainer = new CookieContainer(),
                ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
                {
                    return true;
                }
            };
        });
    }