Web API 中基于令牌的身份验证,无需任何用户界面

Posted

技术标签:

【中文标题】Web API 中基于令牌的身份验证,无需任何用户界面【英文标题】:Token based authentication in Web API without any user interface 【发布时间】:2016-12-04 07:19:27 【问题描述】:

我正在用 ASP.Net Web API 开发一个 REST API。我的 API 只能通过非基于浏览器的客户端访问。我需要为我的 API 实现安全性,所以我决定使用基于令牌的身份验证。我对基于令牌的身份验证有相当了解,并阅读了一些教程,但它们都有一些用于登录的用户界面。我不需要任何 UI 来登录,因为登录详细信息将由客户端通过 HTTP POST 传递,HTTP POST 将从我们的数据库获得授权。如何在我的 API 中实现基于令牌的身份验证?请注意 - 我的 API 将被高频访问,因此我还必须注意性能。 如果我能解释得更好,请告诉我。

【问题讨论】:

某人必须在某处输入用户名和密码才能进行初始验证;您是否建议获得您的应用程序副本的任何人都将使用相同的用户名和密码?如果是这种情况,您是否打算在代码中硬编码用户名和密码值? 我可以有多个注册用户,因此初始登录详细信息将由他们通过 HTTP POST 传递。接下来是什么? 这没有任何意义。服务器如何将凭据传递给客户端?服务器应该如何知道哪个客户端是哪个? 您必须以编程方式进行身份验证,以您喜欢的方式将凭据传递给您的身份验证服务,这种方式不需要您的用户界面,这将向您传回一个令牌。然后,您可以像往常一样使用此令牌拨打电话。 @Claies 很抱歉造成混乱。这个想法是让客户端传递登录详细信息,我的 API 生成一个令牌。这可行吗?如果有其他方法,请告诉我。 【参考方案1】:

ASP.Net Web API 已经内置了授权服务器。 当您使用 Web API 模板创建新的 ASP.Net Web 应用程序时,您可以在 Startup.cs 中看到它。

OAuthOptions = new OAuthAuthorizationServerOptions

    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // In production mode set AllowInsecureHttp = false
    AllowInsecureHttp = true
;

您所要做的就是在查询字符串中发布 URL 编码的用户名和密码。

/Token/userName=johndoe%40example.com&password=1234&grant_type=password

如果你想了解更多细节,可以观看 User Registration and Login - Angular Front to Back with Web API by Deborah Kurata.

【讨论】:

所以我将在 HTTP 标头/正文中使用用户名和密码向 /TOKEN 创建一个 POST 请求?我将为我的应用程序数据库中的所有用户提供用户名和哈希密码。我应该如何实现这个? 你需要 ASP.Net Identity(我相信你已经有了)。如果没有,请创建一个 ASP.Net Web API 项目并查看源代码。 什么是 grant_type=password ?请分享知识。谢谢 我认为在查询字符串中输入用户名和密码是个坏主意。 @frenchie grant_type=password 是 OAuth 2.0 - 资源所有者密码凭据授予类型。这不是我们在浏览器的导航栏中看到的。【参考方案2】:

我认为 MVC 和 Web Api 之间的区别存在一些混淆。简而言之,对于 MVC,您可以使用登录表单并使用 cookie 创建会话。对于 Web Api,没有会话。这就是您要使用令牌的原因。

您不需要登录表单。 Token 端点就是您所需要的。就像 Win 描述的那样,您会将凭据发送到处理它的令牌端点。

这里有一些用于获取令牌的客户端 C# 代码:

    //using System;
    //using System.Collections.Generic;
    //using System.Net;
    //using System.Net.Http;
    //string token = GetToken("https://localhost:<port>/", userName, password);

    static string GetToken(string url, string userName, string password) 
        var pairs = new List<KeyValuePair<string, string>>
                    
                        new KeyValuePair<string, string>( "grant_type", "password" ), 
                        new KeyValuePair<string, string>( "username", userName ), 
                        new KeyValuePair<string, string> ( "Password", password )
                    ;
        var content = new FormUrlEncodedContent(pairs);
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        using (var client = new HttpClient()) 
            var response = client.PostAsync(url + "Token", content).Result;
            return response.Content.ReadAsStringAsync().Result;
        
    

为了使用令牌,请将其添加到请求的标头中:

    //using System;
    //using System.Collections.Generic;
    //using System.Net;
    //using System.Net.Http;
    //var result = CallApi("https://localhost:<port>/something", token);

    static string CallApi(string url, string token) 
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        using (var client = new HttpClient()) 
            if (!string.IsNullOrWhiteSpace(token)) 
                var t = JsonConvert.DeserializeObject<Token>(token);

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + t.access_token);
            
            var response = client.GetAsync(url).Result;
            return response.Content.ReadAsStringAsync().Result;
        
    

Token 在哪里:

//using Newtonsoft.Json;

class Token

    public string access_token  get; set; 
    public string token_type  get; set; 
    public int expires_in  get; set; 
    public string userName  get; set; 
    [JsonProperty(".issued")]
    public string issued  get; set; 
    [JsonProperty(".expires")]
    public string expires  get; set; 

现在是服务器端:

在 Startup.Auth.cs 中

        var oAuthOptions = new OAuthAuthorizationServerOptions
        
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider("self"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            // https
            AllowInsecureHttp = false
        ;
        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(oAuthOptions);

在 ApplicationOAuthProvider.cs 中实际授予或拒绝访问的代码:

//using Microsoft.AspNet.Identity.Owin;
//using Microsoft.Owin.Security;
//using Microsoft.Owin.Security.OAuth;
//using System;
//using System.Collections.Generic;
//using System.Security.Claims;
//using System.Threading.Tasks;

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider

    private readonly string _publicClientId;

    public ApplicationOAuthProvider(string publicClientId)
    
        if (publicClientId == null)
            throw new ArgumentNullException("publicClientId");

        _publicClientId = publicClientId;
    

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

        var user = await userManager.FindAsync(context.UserName, context.Password);
        if (user == null)
        
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        

        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager);
        var propertyDictionary = new Dictionary<string, string>   "userName", user.UserName  ;
        var properties = new AuthenticationProperties(propertyDictionary);

        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        // Token is validated.
        context.Validated(ticket);
    

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        
        return Task.FromResult<object>(null);
    

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    
        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
            context.Validated();

        return Task.FromResult<object>(null);
    

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    
        if (context.ClientId == _publicClientId)
        
            var expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                context.Validated();
        
        return Task.FromResult<object>(null);
    


如您所见,检索令牌不涉及控制器。事实上,如果你只想要一个 Web Api,你可以删除所有的 MVC 引用。我已经简化了服务器端代码以使其更具可读性。您可以添加代码来升级安全性。

确保您只使用 SSL。实现 RequireHttpsAttribute 以强制执行此操作。

您可以使用 Authorize / AllowAnonymous 属性来保护您的 Web Api。此外,您可以添加过滤器(如 RequireHttpsAttribute)以使您的 Web Api 更安全。我希望这会有所帮助。

【讨论】:

您说“对于 MVC,您可以使用登录表单并使用 cookie 创建会话。对于 Web Api 没有会话”,但表单身份验证可以在 Web api 中实现。因此客户端可以将凭据发送到 web api,web api 将向客户端发出 auth cookie。对于所有后续调用,客户端必须将身份验证 cookie 传递给 web api……我想这是可能的。 按如下方式选择您的代码。 new KeyValuePair&lt;string, string&gt;( "grant_type", "password" ), new KeyValuePair&lt;string, string&gt;( "username", userName ), new KeyValuePair&lt;string, string&gt; ( "Password", password ) 是什么意思 grant_type=password ? 我们可以与 grant_type instead of password 一起使用什么其他选项?请分享知识。谢谢 @MonojitSarkar 这些值是 OAuth 2.0 规范的一部分。特别是tools.ietf.org/html/rfc6749。可以在这里找到一个很好的规范摘要:docs.identityserver.io/en/release/intro/specs.html 但是如何在下一个过程中检查这个令牌?意味着我们是否将令牌存储到数据库中?登录成功后如何维护token? 如何将其配置为父站点使用 Windows 身份验证的子站点

以上是关于Web API 中基于令牌的身份验证,无需任何用户界面的主要内容,如果未能解决你的问题,请参考以下文章

Openfire 服务器:使用令牌而不是用户名/密码进行身份验证

ASP.NET MVC Web API 身份验证令牌安全问题

用于 aspnet-core web api 的基于令牌的身份验证

没有用户模型的 Django 基于令牌的身份验证

无需身份验证令牌即可访问 Google 电子表格 API

无需最终用户身份验证的 Youtube API v3