65.9K
CodeProject 正在变化。 阅读更多。
Home

用于使用旧版用户数据库的 Web API 的基于令牌的身份验证

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (18投票s)

2017年5月18日

CPOL

4分钟阅读

viewsIcon

85372

用于 Web API 的基于令牌的身份验证

引言

本文详细介绍了如何在使用 OAuth 和 OWIN 进行基于令牌的身份验证,并且应用程序正在使用存储用户凭据(以遗留格式存储)的自定义数据库时进行身份验证。

背景

近来,我一直在阅读有关 ASP.NET Web API “基于令牌的身份验证”的文章。几乎所有文章都使用 ASP.NET Identity 来进行用户管理功能。(它会创建 ASPNET* 表来管理用户、角色、组等)。

但我找不到任何文章能说明如何为已包含自定义用户管理表的现有数据库使用 OWIN & OAuth “基于令牌的身份验证”。当需要将 OAuth 身份验证应用于已具备自身用户管理功能的遗留系统时,这一点至关重要。

因此,我在分析 Visual Studio 为 Web API Individual Accounts 身份验证提供的标准模板并对其进行调整以使用自定义 DB 用户表后,写下了这篇文章。我在此使用了 Visual Studio 2015。

Using the Code

如上所述,我们的问题是如何让现有的数据库用户表(存储 UserNamePassword)利用基于令牌的身份验证(通过 OAuth 和 OWIN)。因此,我们典型的 DB 查询将如下所示。(目前我未考虑密码加密。这留给您的应用程序逻辑处理。)

让我们开始创建一个标准的 ASP.NET Web API 项目,如下所示。请从“新建项目”模板中选择“Web API”选项。请注意,我们使用的是“无身份验证”选项,因为我们将手动配置它。

由于我们要使用 **OAuth 进行身份验证** 和 **Entity Framework 进行 DB 访问**,请安装以下 Nuget 包:(它们将自动安装其依赖项。)

EntityFramework
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Security
Microsoft.AspNet.Identity.Owin
Microsoft.Owin.Host.SystemWeb

**接下来**,我们需要添加连接到我们的 DB 用户表的 Entity Framework 模型。我不会详细介绍,因为这是添加 ADO.NET Entity Data Model 的标准过程。生成的 EDMX 文件应如下所示

**接下来**,我们需要添加 OWIN Startup 类。它将是一个 partial 类。在 App_Start 文件夹下添加“Startup.Auth.cs”,并将以下代码粘贴到其中

public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }

        // For more information on configuring authentication, 
        // please visit http://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the application for OAuth based flow
            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = 
                new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true
            };

            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthBearerTokens(OAuthOptions);
        }
    }

**再次**,在项目级别添加“Startup.cs”类,并将以下代码粘贴到其中。将 namespace string 替换为您的 project namespace

[assembly: OwinStartup(typeof(OAuth_Custom_DB.Startup))]
namespace OAuth_Custom_DB
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

**注意**:由于 Startup 是一个 partial class,因此其 namespaceStartup.csStartup.Auth.cs 中必须相同。

**接下来**,我们需要告知 Web API 应用程序使用 Bearer 身份验证,而不是默认主机身份验证。因此,请将以下行添加到 WebApiConfig.cs

// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

**接下来**,我们需要配置 OAuth 服务器和选项。因此,请在其中添加一个名为“Providers”的文件夹和一个名为“ApplicationOAuthProvider.cs”的类文件。

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)
        {
            /*** Replace below user authentication code as per your Entity Framework Model ***
            using (var obj = new UserDBEntities())
            {
                
                tblUserMaster entry = obj.tblUserMasters.Where
                <tblUserMaster>(record => 
                record.User_ID == context.UserName && 
                record.User_Password == context.Password).FirstOrDefault();

                if (entry == null)
                {
                    context.SetError("invalid_grant", 
                    "The user name or password is incorrect.");
                    return;
                }                
            }
            */

            ClaimsIdentity oAuthIdentity = 
            new ClaimsIdentity(context.Options.AuthenticationType);
            ClaimsIdentity cookiesIdentity = 
            new ClaimsIdentity(context.Options.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(context.UserName);
            AuthenticationTicket ticket = 
            new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        }

        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)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> 
            data = new Dictionary<string, string>
            {
                { "userName", userName }
            };
            return new AuthenticationProperties(data);
        }
    }

实际上,这与我们在选择 Web API 项目模板时选择“Individual Accounts”身份验证模式时生成的相同文件。唯一的区别是我们正在更改“GrantResourceOwnerCredentials”方法的实现。方法内的代码应如下所示

/*** Replace below user authentication code as per your Entity Framework Model ***
using (var obj = new UserDBEntities())
            {
                
                tblUserMaster entry = obj.tblUserMasters.Where<tblUserMaster>
                (record => record.User_ID == context.UserName &&
                record.User_Password == context.Password).FirstOrDefault();

                if (entry == null)
                {
                    context.SetError("invalid_grant", 
                    "The user name or password is incorrect.");
                    return;
                }                
            }
            */

            ClaimsIdentity oAuthIdentity = 
            new ClaimsIdentity(context.Options.AuthenticationType);
            ClaimsIdentity cookiesIdentity = 
            new ClaimsIdentity(context.Options.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(context.UserName);
            AuthenticationTicket ticket = 
            new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);

**最后**,为 ValuesController 控制器应用普通的 [Authorize] 属性以进行测试。

就这样……!!!

我们的应用程序已准备好进行基于令牌的身份验证的测试,该身份验证具有自定义用户 ID/密码表。

运行应用程序,以便 API 服务开始运行并准备好被使用。您的网页应如下所示,端口号(localhost 后的部分)除外

现在,让我们直接测试标准的 Web API URL 是否可以在没有授权的情况下工作。

打开 Chrome Postman,输入 Values Controller 的 GET URL。例如 https://:56889/api/Values。正如下面所见,它会给出授权被拒绝的错误,显然是因为没有进行授权。

因此,现在,我们将通过身份验证用户凭据来获取“Bearer Token”。因此,我们的 Token URL 将如下所示:https://:56889/Token。请注意,“/token”是 Startup 类中“ConfigureAuth”方法中指定的“TokenEndPoint”路径。因此,如果将其配置为“/auth/token”,则完整 URL 将是 https://:56889/Auth/Token

如果您查看 Postman 条目,对于 Token URL,我们正在传递三个参数,内容类型为“x-www-form-urlencoded”。其中一个参数是“grant_type”,其值为“password”。这告诉 OAuth 用户想要颁发令牌。

在 POST URL 成功执行后,我们将获得如下所示的令牌颁发

这里发生的情况是,此 URL 调用了 GrantResourceOwnerCredentials 方法。在此方法中,我们编写了使用 Entity Framework 的自定义用户身份验证代码。如果成功,它将进一步执行 TokenEndpoint 方法来颁发一个 bearer 令牌。

请记下此令牌值,然后重新访问 ValuesControllerGET URL。这次,我们将添加一个名为“Authorization”的标头,并将身份验证后获得的令牌值作为其值粘贴,并在前面加上“bearer ”。

现在,此令牌将一直有效,直到我们在 Startup 类中 ConfigureAuth 方法的 OAuthAuthorizationServerOptions 中再次指定的时间。

© . All rights reserved.