【问题标题】:Authorization token not binding in ASP.NET Core授权令牌在 ASP.NET Core 中未绑定
【发布时间】:2018-09-13 09:59:06
【问题描述】:

我这样调用 REST api:

HttpClient client;
var uri = new Uri(Const.GetUserAccount);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("accessToken", App.AccessToken);
var response = await client.GetAsync(uri);

这是我的动作签名(accessToken 为空):

public async Task<ActionResult> GetAccountAsync([FromHeader] string accessToken)

令牌在 Request.Headers.HeaderAuthorization 中,其值为:

"accessToken" + a space + the guid

这似乎很奇怪。不应该有一个名称值对吗?喜欢:

“accessToken”:“theGUID”

这就是它不具有约束力的原因吗?如果是这样,我该如何正确传递它?如果没有,我做错了什么?

【问题讨论】:

  • 我觉得参数应该叫authorization而不是accessToken
  • [FromHeader] 属性如何知道从哪里获取 accessToken?
  • 你的令牌是什么??智威汤逊?
  • 不,它不应该"accessToken": "theGUID" - 整个标题实际上是Authorization: accessToken theGUID: 划分标头/值对。正如@CamiloTerevinto 所说,您可以将参数更改为authorization,这将为您提供accessToken theGUID 的值。

标签: c# asp.net-core


【解决方案1】:

这就是它不绑定的原因吗?

原因是您的操作方法需要来自请求标头的accessToken

public async Task<ActionResult> GetAccountAsync([FromHeader] string accessToken)

虽然您在请求中没有这样的 AccessToken: xxx_yyy_zzz 标头。

如果您发送如下请求:

GET https://localhost:44323/api/values/account HTTP/1.1
accessToken : xxx_yyy_zzz

ModelBinder 将绑定accessToken

如果是,如何正确传递?

我不确定您为什么要在操作方法中获取accessToken。但是,如果您确实需要通过模型绑定获取访问令牌,那么至少有两种方法可以做到这一点:

一种方法是更改​​您的操作方法以直接获取Authorization 标头:

public async Task<ActionResult> GetAccount2Async([FromHeader] string authorization) 
{
    if (String.IsNullOrEmpty(authorization)) { /* */ }
    if (!authorization.StartsWith("accessToken",StringComparison.OrdinalIgnoreCase)) { /* */ }

    var token = authorization.Substring("accessToken".Length).Trim();
    // ...
}

当您发送标头为 Authorization : accessToken xxx_yyy_zzz 的请求时,它将起作用。

然而,上面的方法并不干净。更好的方法是创建自定义 ModelBinder

首先让我们创建一个虚拟类来保存 accessToken 值:

public class AccessTokenAuthorizationHeader
{
    public string TokenValue { get; set; }
}

这是一个简单的模型绑定器,它将从标头中检索访问令牌:

public class AuthorizationHeaderBinder : IModelBinder
{
    const string DEFAULT_ACCESS_TOKEN_AUTH_HEADER_PREFIX = "accessToken";
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }

        var modelName = bindingContext.BinderModelName;
        if (string.IsNullOrEmpty(modelName)) { modelName = DEFAULT_ACCESS_TOKEN_AUTH_HEADER_PREFIX; }

        var authorization = bindingContext.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
        if (String.IsNullOrWhiteSpace(authorization)) {
            return Task.CompletedTask;
        }
        if (!authorization.StartsWith(modelName, StringComparison.OrdinalIgnoreCase)) {
            return Task.CompletedTask;
        }
        var token = authorization.Substring(modelName.Length).Trim();

        bindingContext.Result = ModelBindingResult.Success(new AccessTokenAuthorizationHeader() {
            TokenValue =token,
        });
        return Task.CompletedTask;
    }
}

最后,用ModelBinderAttribute装饰之前的AccessTokenAuthorizationHeader

[ModelBinder(BinderType =typeof(AuthorizationHeaderBinder))]
public class AccessTokenAuthorizationHeader
{
    public string TokenValue { get; set; }
}

现在我们可以自动绑定了:

[HttpGet("Account3")]
public async Task<ActionResult> GetAccount3Async(AccessTokenAuthorizationHeader accessToken) {
    var result =new JsonResult(accessToken?.TokenValue);
    return result;
}

让我们用请求集来测试它:

GET https://localhost:44323/api/values/account3 HTTP/1.1
Authorization : accessToken 111111

响应将是:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcOVw5LTEzXFNPLkF1dGhvcml6YXRpb25IZWFkZXJcQXBwXEFwcFxhcGlcdmFsdWVzXGFjY291bnQz?=
X-Powered-By: ASP.NET
Date: Thu, 13 Sep 2018 01:54:25 GMT

"111111"

【讨论】:

  • 您还可以通过任何操作方法通过HttpContext.Request.Headers 访问标题。无需自定义绑定。
  • @Brad 好吧,这正是我在第一种方法中所做的。但是,如果在这种情况下,您必须验证 Authorization 的标头是 null 还是空,无论是 Authorization : accessToken xxx_yyy_zzzAuthorization : access-Token xxx_yyy_zzz 或其他东西。如果有多个操作方法需要 accessToken ,我相信我们的头会很痛。
  • 您可能希望使用 ModelBinding,以便更轻松地进行单元测试。您可以直接在方法调用中传入字符串,而不必设置上下文、请求、标头集合和实际标头名称/值。当然你 canevery 单元测试和 every controller method 中完成所有这些,但为什么不编写一次并在任何地方重复使用呢?
  • @NoRefundsNoReturns 感谢您的建议。但是你能详细说明一下吗?我确实在我的第二种方法中创建了一个 ModelBinder。
  • 我的评论主要针对@Brad,他的方法需要你为每个单元测试设置很多额外的东西。
猜你喜欢
  • 2021-10-05
  • 2020-07-09
  • 1970-01-01
  • 2016-05-17
  • 2020-09-17
  • 2023-03-25
  • 2023-03-03
  • 2023-04-01
  • 1970-01-01
相关资源
最近更新 更多