【问题标题】:Issue Application Id for each kind of Client Application为每种客户端应用程序颁发应用程序 ID
【发布时间】:2015-12-01 16:26:51
【问题描述】:

我正在使用Microsoft OwinASP.NET WebApi 为我的客户端应用程序进行身份验证和授权过程。此外,身份验证服务器由HTTPS 保护。我已经阅读了几篇关于使用Microsoft Owin 的文章,其中我选择实施的一篇是: Token Based Authentication using ASP.NET Web API 2, Owin, and Identity

我的项目和那个实现之间存在一些差异:

  1. 如果请求是由我在手机上的应用程序而不是任何其他设备或工具(如 Fiddler)发送的,我需要识别我的客户。我认为一个选项可能是通过来自移动应用程序的每个请求发送一个应用程序 ID。但我不知道应该如何以及在哪里验证身份验证服务器应用程序中的请求。这对于注册用户非常重要:

        [AllowAnonymous]
        [Route("Register")]
        public async Task<IHttpActionResult> Register(UserModel userModel)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
    
            IdentityResult result = await _repo.RegisterUser(userModel);
    
            IHttpActionResult errorResult = GetErrorResult(result);
    
            if (errorResult != null)
            {
                return errorResult;
            }
    
            return Ok();
        }
    

    我不想让不可靠的设备,即除了移动应用之外的客户端,调用这个方法。

  2. 我需要让匿名用户从网站购买一些产品,但我不知道在不进行身份验证的情况下为匿名用户颁发令牌的最佳做法是什么。

【问题讨论】:

    标签: authentication asp.net-web-api owin


    【解决方案1】:

    如果您想识别您的客户并对其进行授权,您可以覆盖方法ValidateClientAuthentication

    在您链接的 Taiseer 示例中,您会发现一些代码:

    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }
    

    还有一个说明:

    你注意到这个类继承自类 “OAuthAuthorizationServerProvider”,我们重写了两个方法 “ValidateClientAuthentication”和“GrantResourceOwnerCredentials”。 第一种方法负责验证“客户”,在我们的 如果我们只有一个客户,所以我们总是会返回它经过验证的 成功。

    如果您想验证客户端,您必须在其中添加一些逻辑。
    通常,您会在 http 请求的标头中传递 clientIdclientSecret,以便您可以使用一些数据库参数验证客户端的请求。

    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        string clientId = string.Empty;
        string clientSecret = string.Empty;
    
        if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
        {
            context.TryGetFormCredentials(out clientId, out clientSecret);
        }
    
        if (context.ClientId == null)
        {
            context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
            context.Rejected();
    
            return;
        }
    
        try
        {
            // You're going to check the client's credentials on a database.
            if (clientId == "MyApp" && clientSecret == "MySecret")
            {
                context.Validated(clientId);
            }
            else
            {
                // Client could not be validated.
                context.SetError("invalid_client", "Client credentials are invalid.");
                context.Rejected();
            }
        }
        catch (Exception ex)
        {
            string errorMessage = ex.Message;
            context.SetError("server_error");
            context.Rejected();
        }
    
        return;
    }
    

    在上面的示例中,您将尝试提取在请求标头中发送的客户端凭据:

    if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
    {
        context.TryGetFormCredentials(out clientId, out clientSecret);
    }
    

    并验证它们:

    // You're going to check the client's credentials on a database.
    if (clientId == "MyApp" && clientSecret == "MySecret")
    {
        context.Validated(clientId);
    }
    

    如果客户端发送了错误的请求头,你需要拒绝该请求:

    context.SetError("invalid_client", "Client credentials are invalid.");
    context.Rejected();
    

    方法ValidateClientAuthenticationGrantResourceOwnerCredentials之前处理。通过这种方式,您可以扩展它并传递 GrantResourceOwnerCredentials 一些您可能需要的额外信息。

    在我的一个应用程序中,我创建了一个类:

    class ApplicationClient
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string ClientSecretHash { get; set; }
        public OAuthGrant AllowedGrant { get; set; }
        public DateTimeOffset CreatedOn { get; set; }
    }
    

    在我检查了 clientId 并且密码正常后,我在 ValidateClientAuthentication 中使用它:

    if (clientId == "MyApp" && clientSecret == "MySecret")
    {
        ApplicationClient client = new ApplicationClient();
        client.Id = clientId;
        client.AllowedGrant = OAuthGrant.ResourceOwner;
        client.ClientSecretHash = new PasswordHasher().HashPassword("MySecret");
        client.Name = "My App";
        client.CreatedOn = DateTimeOffset.UtcNow;
    
        context.OwinContext.Set<ApplicationClient>("oauth:client", client);
    
        context.Validated(clientId);
    }
    

    如你所见

    context.OwinContext.Set<ApplicationClient>("oauth:client", client);
    

    我正在设置一个 Owin 变量,稍后我可以读取它。现在,在您的 GrantResourceOwnerCredentials 中,您可以读取该变量以备不时之需:

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        ApplicationClient client = context.OwinContext.Get<ApplicationClient>("oauth:client");
        ...
    }
    

    现在,如果您想获取不记名令牌 - 您将用于所有安全 API 调用 - 您需要对您的 clientIdclientSecret (base64) 进行编码并将其传递到请求:

    使用 jquery 的 ajax 请求看起来像这样:

    var clientId = "MyApp";
    var clientSecret = "MySecret";
    
    var authorizationBasic = $.base64.btoa(clientId + ':' + clientSecret);
    
    $.ajax({
            type: 'POST',
            url: '<your API token validator>',
            data: { username: 'John', password: 'Smith', grant_type: 'password' },
            dataType: "json",
            contentType: 'application/x-www-form-urlencoded; charset=utf-8',
            xhrFields: {
               withCredentials: true
            },
            headers: {
               'Authorization': 'Basic ' + authorizationBasic
            },
            beforeSend: function (xhr) {
            },
            success: function (result) {
            var token = result.access_token;
            },
            error: function (req, status, error) {
                alert(error);
            }
    });
    

    如您所见,我还在请求正文中添加了用户名和密码 - 以及授权类型:

    data: { username: 'John', password: 'Smith', grant_type: 'password' }
    

    以便服务器能够验证客户端(clientId + clientSecret)和用户(用户名 + 密码)。

    如果请求成功,你应该取回一个有效的令牌:

    oAuth.Token = result.access_token;
    

    您可以将其存储在某处以用于以下请求。

    现在您可以将此令牌用于对 api 的所有请求:

    $.ajax({
        type: 'GET',
        url: 'myapi/fetchCustomer/001',
        data: { },
        dataType: "json",
        headers: {
        'Authorization': 'Bearer ' + oAuth.Token
        },
        success: function (result) {
        // your customer is in the result.
       },
        error: function (req, status, error) {
        alert(error);
        }
    });
    

    在启动期间您可能想要添加到 API 的另一件事是 SuppressDefaultHostAuthentication

    config.SuppressDefaultHostAuthentication();
    

    这是HttpConfiguration的扩展方法。由于您使用的是不记名令牌,因此您希望禁止使用标准的基于 cookie 的身份验证机制。

    Taiseer 写了另一个系列的articles 值得一读,他解释了所有这些事情。

    我创建了一个github repo,您可以在其中查看它的工作原理。
    Web API 是自托管的,有两个客户端:jQuery 和控制台应用程序。

    【讨论】:

    • 多么完美且结构合理的答案!我会测试你的解决方案。谢谢。
    • 告诉我进展如何。我花了很长时间试图弄清楚所有这些部分是如何组合在一起的。这并不容易,尤其是因为在线文档不多。
    • 是的,你说得对,因为缺乏文档,我花了两天时间学习结构。我会报告结果:)
    • 我已经仔细检查了您的示例代码,如果我错了,请纠正我: 1. 发送带有基本身份验证标头的请求,并带有应用程序 ID 和密码以获取令牌。 2. 其余请求将通过检查头请求中放置的 Bearer 令牌来授权。(例如 api/customer/get)如果我理解解决方案正确,请告诉我。
    • 正确。第一个请求将凭据(用户名和密码)连同 base64 标头(clientId 和 clientSecret)一起发布到您的 API http://your-api/oauth/Token。你应该在那里得到一个有效的令牌。要跟踪对 API 的请求,应仅使用标头 headers: { 'Authorization': 'Bearer ' + oAuth.Token } 中的令牌。我会尝试扩展我的答案。
    猜你喜欢
    • 2020-09-28
    • 1970-01-01
    • 2016-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    相关资源
    最近更新 更多