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

使用 Jwt 在 Asp.Net Web Api 2.2 中进行简单身份验证

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (14投票s)

2015年11月3日

CPOL

4分钟阅读

viewsIcon

111208

在本文中,我们将探讨如何在 Asp.Net Web Api 2.2 中使用 Json Web 令牌作为身份验证机制。

引言

在企业环境中,实现基于令牌的身份验证的最佳方法是使用单独的安全令牌服务 (STS)。当用户登录到客户端应用程序时,客户端应用程序会将这些凭据与其自身的身份信息一起发送给 STS 进行验证。STS 会检查这些凭据并检查用户对请求资源的权限。如果一切正常,STS 会将包含用户对请求资源具有的声明或权限信息的令牌颁发给客户端应用程序。收到令牌后,客户端应用程序会将令牌呈现给持有资源的服务器,该服务器会根据用户的权限,允许其访问安全资源。

您可以使用的真实 STS 示例是 Windows Azure Active Directory,或者您可以使用 ThinkTecture 身份服务器实现自己的 STS。但有时,作为开发人员,我们没有资源和时间来实现自己的 STS 或托管单独的身份服务器,更不用说深入研究 ThinkTecture 身份服务器的内部工作原理了。大多数时候,我们只是想要一个简单的表单身份验证般的基础设施来处理我们的身份验证。本文就是关于这个的,如何在不使用单独的身份服务器的情况下实现具有 JWT 的基于令牌的身份验证机制。

背景

在本文中,我们将探讨如何在 Asp.Net Web Api 中使用 JWT 来实现基于令牌的身份验证。我们将采取一种实际的方法,而不是深入探讨基于令牌的身份验证的内部细节。互联网上有很多关于它的资料可供阅读。那么,什么是 JSON Web 令牌?它们是基于标准的令牌身份验证机制,基于 Internet Engineering Task Force (IETF) 的规范,您可以在此处找到,Web 应用程序使用它们在依赖方之间传递声明。

简单来说,引用 W3C 团队的话,基于令牌的身份验证:

允许用户输入其用户名和密码以获取允许他们获取特定资源的令牌 - 而无需使用其用户名和密码。一旦获得了令牌,用户就可以将令牌 - 该令牌提供对特定资源的访问权限一段时间 - 提供给远程站点。使用某种形式的身份验证:标头、GET 或 POST 请求,或某种 cookie,站点然后可以确定所请求的访问级别。

JWT 看起来是怎样的?它是一个 JSON 编码的字符串,由三个用点分隔的 base64 编码字符串组成。这三个部分是标头、有效负载和签名。有关 JWT 结构的更多信息,请阅读本文。如果您想尝试编码和解码 JWT,请在此处进行。

实现

我们将使用 Asp.Net Web Api 和一个名为 Jwt 的库来实现一个基本的身份验证解决方案。

Register

当用户使用电子邮件和密码在我们的应用程序上注册时,我们会将他们的详细信息保存到我们的数据库,使用保存的信息创建令牌(一个 jwt),然后将令牌连同新用户详细信息一起发送回客户端应用程序。新用户详细信息将是我们以 REST 方式再次访问用户的允许信息。现在他们有了令牌,客户端应用程序将在用户通过 Authorization 标头向服务器发出的每个请求中包含此令牌。如果我们想访问安全资源,我们将检查令牌以查看他们是否具有权限,然后允许他们访问资源。

        [AllowAnonymous]
        [Route("signup")]
        [HttpPost]
        public HttpResponseMessage Register(RegisterViewModel model)
        {
            HttpResponseMessage response;
            if (ModelState.IsValid)
            {
                var existingUser = db.Users.FirstOrDefault(u => u.Email == model.Email);
                if (existingUser != null)
                {
                    return Request.CreateResponse(HttpStatusCode.BadRequest, "User already exist.");
                }

                //Create user and save to database
                var user = CreateUser(model);
                object dbUser;

                //Create token
                var token = CreateToken(user, out dbUser);
                response = Request.CreateResponse(new {dbUser, token});
            }
            else
            {
                response = Request.CreateResponse(HttpStatusCode.BadRequest, new {success = false});
            }

            return response;
        }

登录

与注册方法类似,当返回用户登录到我们的应用程序时,我们会检查数据库,看他们的凭据是否有效,如果有效,我们会创建一个令牌并将其再次发送回客户端应用程序以及他们的用户详细信息。从那时起,令牌将通过 Authorization 标头包含在他们发出的每个请求中。请参见下面的代码。

[AllowAnonymous]
        [Route("signin")]
        [HttpPost]
        public HttpResponseMessage Login(LoginViewModel model)
        {
            HttpResponseMessage response = null;
            if (ModelState.IsValid)
            {
                var existingUser = db.Users.FirstOrDefault(u => u.Email == model.Email);

                if (existingUser == null)
                {
                    response = Request.CreateResponse(HttpStatusCode.NotFound);
                }
                else
                {
                    var loginSuccess =
                        string.Equals(EncryptPassword(model.Password, existingUser.Salt),
                            existingUser.PasswordHash);

                    if (loginSuccess)
                    {
                        object dbUser;
                        var token = CreateToken(existingUser, out dbUser);
                        response = Request.CreateResponse(new {dbUser, token});
                    }
                }
            }
            else
            {
                response = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
            return response;
        }

身份验证机制

那么我们如何检查他们是否可以访问安全资源?我们通过 Asp.Net Web Api 消息处理程序来做到这一点。基本上,消息处理程序会检查从客户端应用程序发送的请求是否具有包含有效令牌的 Authorization 标头,如果没有,则将其作为正常请求传递。如果它具有授权标头,我们会检查令牌中传递的任何声明或角色,并将执行的标识 Principal 设置为包含我们令牌中声明的新 ClaimsPrincipal。一如既往地将请求向下传递。

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                    CancellationToken cancellationToken)
        {
            HttpResponseMessage errorResponse = null;

            try
            {
                IEnumerable<string> authHeaderValues;
                request.Headers.TryGetValues("Authorization", out authHeaderValues);

                if (authHeaderValues == null)
                    return base.SendAsync(request, cancellationToken); // cross fingers

                var bearerToken = authHeaderValues.ElementAt(0);
                var token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;

                //var secret = ConfigurationManager.AppSettings.Get("jwtKey");
                var secret = "secretKey";

                Thread.CurrentPrincipal = ValidateToken(
                    token,
                    secret,
                    true
                    );

                if (HttpContext.Current != null)
                {
                    HttpContext.Current.User = Thread.CurrentPrincipal;
                }
            }
            catch (SignatureVerificationException ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message);
            }
            catch (Exception ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
            }

            return errorResponse != null
                ? Task.FromResult(errorResponse)
                : base.SendAsync(request, cancellationToken);
        }

关注点

为了测试这一点,我创建了一个包含书籍列表的安全资源。我在控制器上添加了 Authorize 属性。不登录,我得到了以下结果。

登录后,我能够访问超级安全的图书资源。

结论

本文的源代码可在 GitHub 上找到。去尝试一下吧。

谢谢

历史

发布于 2015 年 11 月 03 日

© . All rights reserved.