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


68

我正在ASP.Net Web API中开发REST API。我的API仅可通过非基于浏览器的客户端访问。我需要为我的API实现安全性,因此我决定采用基于令牌的身份验证。我对基于令牌的身份验证有一定的了解,并且阅读了一些教程,但是它们都有一些用于登录的用户界面。我不需要任何UI进行登录,因为登录详细信息将由客户端通过HTTP POST传递,而HTTP POST将从我们的数据库中获得授权。如何在我的API中实现基于令牌的身份验证?请注意-我的API将以高频率访问,因此我也必须注意性能。请让我知道是否可以更好地解释它。


1
某人,某处必须输入用户名和密码才能进行初始验证;您是否建议获得应用程序副本的任何人都将使用相同的用户名和密码?如果是这样,您是否打算在代码中对用户名和密码值进行硬编码?
Claies

我可以有多个注册用户,因此初始登录详细信息将由他们通过HTTP POST传递。接下来是什么?
Souvik Ghosh'7

那没有任何意义。服务器如何将凭据传递给客户端?服务器应该如何知道哪个客户端是哪个客户端?
Claies

1
您将需要以编程方式进行身份验证,以不需要用户界面的首选方式将凭据传递到Authentication服务,这将使您返回令牌。然后,您可以像平常一样使用此令牌进行呼叫。
plusheen'7

@Claies抱歉让您感到困惑。想法是让客户端传递登录详细信息,然后我的API生成令牌。这可行吗?如果有其他方法,请告诉我。
Souvik Ghosh

Answers:


87

我认为MVC和Web Api之间的区别有些混乱。简而言之,对于MVC,您可以使用登录表单并使用Cookie创建会话。对于Web Api,没有会话。这就是为什么要使用令牌。

您不需要登录表单。令牌端点就是您所需要的。就像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;
        }
    }

令牌在哪里:

//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更安全。我希望这有帮助。


2
你说“对于MVC,您可以使用登录表单并使用cookie创建会话。对于Web Api,则没有会话”,但可以在Web api中实现表单身份验证。因此客户端可以将凭据发送到Web api,Web api会向客户端发布身份验证cookie。对于所有随后的呼叫客户端,必须将身份验证cookie传递给Web api……我想这是可能的。
Monojit Sarkar'8

如下选择代码。 new KeyValuePair<string, string>( "grant_type", "password" ), new KeyValuePair<string, string>( "username", userName ), new KeyValuePair<string, string> ( "Password", password ) 这是什么平均值grant_type=password ?什么其他的选择,我们可以使用grant_type instead of password?请分享知识。谢谢
Monojit Sarkar

@MonojitSarkar这些值是OAuth 2.0规范的一部分。特别是tools.ietf.org/html/rfc6749。可以在这里找到规格的详细摘要:docs.identityserver.io/en/release/intro/specs.html
Ruard van Elburg

但是如何在下一步中使用此令牌进行检查?意味着我们是否将令牌存储到数据库中?成功登录后如何维护令牌?
user8478

如何将其配置为其父级使用Windows身份验证的子站点
mercu 18-10-10

20

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

如果您想了解更多详细信息,可以观看 Deborah Kurata撰写的“用户注册和登录-使用Web API从前到后成角度”


因此,我将在HTTP标头/正文中使用用户名和密码创建对/ TOKEN的POST请求?我将为我的应用程序数据库中的所有用户提供用户名和哈希密码。我应该如何实施呢?
Souvik Ghosh

您需要ASP.Net Identity (我相信您已经拥有一个)。如果没有,请创建一个ASP.Net Web API项目并查看源代码。
赢得

2
什么是grant_type = password?请分享知识。谢谢
Monojit Sarkar

4
我认为在查询字符串中输入用户名和密码是一个不好的想法。
frenchie

2
@frenchiegrant_type=password是OAuth 2.0-资源所有者密码凭据授予类型。这不是我们在浏览器的导航栏中看到的。
赢得
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.