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

ASP.NET CORE 使用 JWT (无 Cookie) 进行令牌身份验证和授权 - 第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (34投票s)

2019 年 6 月 30 日

CPOL

12分钟阅读

viewsIcon

231890

downloadIcon

4965

本文介绍如何在 ASP.NET CORE 中使用 JWT 实现令牌身份验证和授权。

目录

引言

在开始讨论主题之前,让我们先简要解释一下身份验证和授权。

身份验证 (Authentication): 授予用户访问/进入应用程序的权限。这就像给一个人进入建筑物的权限一样。

授权 (Authorization): 这在身份验证之后发生。仅授予用户访问应用程序特定页面的权限。这就像一个人有权限进入一座有 10 层楼的建筑,但只能去第 2 层或第 4 层。

JWT (JSON Web Token)

正如其名,JWToken 是一个 JSON 格式的字符串值。JWToken 为每个有效用户 (身份验证) 发放。令牌在用户登录时仅创建一次。用户将在所有后续的 HTTP 请求中使用该令牌进行授权,直到该用户从应用程序中注销。

ASP.NET Core 中的 JWToken 配置

我们不会深入探讨 JWToken 配置的每个细节。有很多文章对此进行了说明。使用 Microsoft.AspNetCoreAuthentication.JwtBearerMicrosoft.IdentityModel.Tokens 配置 JWT。这在 Startup.csConfigurationServices() 方法中完成。

您可以在下面的代码中看到,令牌配置包含两个部分:services.AddAuthentication()AddJwtBearer()

services.AddAuthentication(): 此部分用于配置我们将要使用的身份验证方案或机制。在这里,我们告诉 ASP.NET Core 使用 JWT Bearer Token 身份验证。这非常重要,因为它将在后面的 Configure() 方法中使用。

AddJwtBearer(): 在此部分,我们配置带有密钥、过期日期、颁发者等的 Token。密钥用于加密和解密令牌。在创建令牌时应使用相同的密钥,这将在“创建令牌”主题中进行说明。

public void ConfigureServices(IServiceCollection services)
{
    services.AddSession(options => {
        options.IdleTimeout = TimeSpan.FromMinutes(60);
    });
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    
    //Provide a secret key to Encrypt and Decrypt the Token
    var SecretKey = Encoding.ASCII.GetBytes
         ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv");
    //Configure JWT Token Authentication
    services.AddAuthentication(auth =>
    {
        auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(token =>
    {
        token.RequireHttpsMetadata = false;
        token.SaveToken = true;
        token.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            //Same Secret key will be used while creating the token
            IssuerSigningKey = new SymmetricSecurityKey(SecretKey),
            ValidateIssuer = true,
            //Usually, this is your application base URL
            ValidIssuer = "https://:45092/",
            ValidateAudience = true,
            //Here, we are creating and using JWT within the same application.
            //In this case, base URL is fine.
            //If the JWT is created using a web service, then this would be the consumer URL.
            ValidAudience = "https://:45092/",
            RequireExpirationTime = true,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
    });
}    

用户模型类

我们需要一个用户模型类来进行登录。创建一个包含用户 ID、密码和其他凭据的用户模型类。在 “Models” 文件夹下创建一个名为 User.cs 的类。

public class User
{
    public string USERID { get; set; }
    public string PASSWORD { get; set; }
    public string FIRST_NAME { get; set; }
    public string LAST_NAME { get; set; }
    public string EMAILID { get; set; }
    public string PHONE { get; set; }
    public string ACCESS_LEVEL { get; set; }
    public string READ_ONLY { get; set; }
}    

创建令牌

步骤 1

让我们创建一个名为 TokenProvider.cs 的类,它将为用户创建/生成令牌。令牌仅创建一次,并在用户注销之前用于所有后续请求。在解决方案的根文件夹下,创建一个名为 TokenProvider.cs 的类。

第二步

在创建 Token 之前,我们需要从登录页面获取 UserID,并检查该用户是否存在于我们的数据库中。出于演示目的,用户列表是硬编码的值,存储在一个列表中。在实际应用中,这会来自数据库或某些数据源。让我们向 TokenProvider.cs 类添加一个属性 (UserList)。此属性是我们用户数据存储,其中包含一些硬编码的值。

//Using hard coded collection list as Data Store for demo purposes
//In reality, User data comes from Database or other Data Source.
private List UserList = new List
{
    new User { USERID = "jsmith@email.com", PASSWORD = "test", 
               EMAILID = "jsmith@email.com", FIRST_NAME = "John", 
               LAST_NAME = "Smith", PHONE = "356-735-2748", 
               ACCESS_LEVEL = "Director", READ_ONLY = "true" },
    new User { USERID = "srob@email.com", PASSWORD = "test", 
               FIRST_NAME = "Steve", LAST_NAME = "Rob", 
               EMAILID = "srob@email.com", PHONE = "567-479-8537", 
               ACCESS_LEVEL = "Supervisor", READ_ONLY = "false" },
    new User { USERID = "dwill@email.com", PASSWORD = "test", 
               FIRST_NAME = "DJ", LAST_NAME = "Will", 
               EMAILID = "dwill@email.com", PHONE = "599-306-6010", 
               ACCESS_LEVEL = "Analyst", READ_ONLY = "false" },
    new User { USERID = "JBlack@email.com", PASSWORD = "test", 
               FIRST_NAME = "Joe", LAST_NAME = "Black", 
               EMAILID = "JBlack@email.com", PHONE = "764-460-8610", 
               ACCESS_LEVEL = "Analyst", READ_ONLY = "true" }
};    

步骤 3

我们需要在令牌中为应用程序设置用户权限 (授权)。在令牌中,我们需要说明用户可以拥有什么级别的权限。用户权限以 Claims 的形式创建。在创建令牌时,我们将在 Claims 对象集合中设置用户权限,并将其分配给 Token。这些 Claims 值将用于在控制器中授予用户权限/授权用户。在 MVC 控制器的操作方法中,我们将使用 “ACCESS_LEVEL” 和 “READ_ONLY” 声明来设置用户权限。出于演示目的,用户声明是硬编码的。在这里,您可以连接到您的数据库并获取用户权限。

让我们向 TokenProvider.cs 类添加一个方法 (GetUserClaims()) 来获取用户权限级别并构建声明对象集合。

//Using hard coded values in claims collection list as Data Store for demo. 
//In reality, User data comes from Database or other Data Source.
private IEnumerable GetUserClaims(User user)
{
    IEnumerable claims = new Claim[]
    {
        new Claim(ClaimTypes.Name, user.FIRST_NAME + " " + user.LAST_NAME),
        new Claim("USERID", user.USERID),
        new Claim("EMAILID", user.EMAILID),
        new Claim("PHONE", user.PHONE),
        new Claim("ACCESS_LEVEL", user.ACCESS_LEVEL.ToUpper()),
        new Claim("READ_ONLY", user.READ_ONLY.ToUpper())
    };
    return claims;
}    

步骤 4

现在是时候为用户创建令牌了。首先,从登录页面获取用户 ID,并检查该用户是否存在于上面声明的 UserList 集合属性中。如果用户 ID 在列表中,则说明用户已注册。否则,身份验证失败。不要发放令牌。

其次,从登录页面获取密码,并检查密码是否与 UserList 中的密码匹配。如果匹配,则为用户创建令牌。否则,身份验证失败,令牌未创建/发放。

要创建 JWToken,我们将使用两个命名空间:System.IdentityModel.Tokens.JwtMicrosoft.IdentityModel.Tokens。让我们使用 JwtSecurityToken() 类创建令牌 (此处不介绍令牌创建的详细信息,有很多文章解释 JWT 令牌创建)。创建令牌时,用户声明值会被加载到令牌的 “claims” 属性中。我们调用上面的 GetUserClaims() 函数来加载用户的声明。TokenLoginUser() 方法中创建,该方法接受 UserIDPassword 作为输入参数。

让我们创建一个名为 LoginUser() 的函数,该函数在 TokenProvider.cs 中接受 UserIDPassword 作为输入参数。

public string LoginUser(string UserID, string Password)
{
    //Get user details for the user who is trying to login
    var user = UserList.SingleOrDefault(x => x.USERID == UserID);
    
    //Authenticate User, Check if it’s a registered user in Database
    if (user == null)
        return null;
        
    //If it's registered user, check user password stored in Database 
    //For demo, password is not hashed. Simple string comparison 
    //In real, password would be hashed and stored in DB. Before comparing, hash the password
    if (Password == user.PASSWORD)
    {        
        //Authentication successful, Issue Token with user credentials
        //Provide the security key which was given in the JWToken configuration in Startup.cs
        var key = Encoding.ASCII.GetBytes
                  ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv"); 
        //Generate Token for user 
        var JWToken = new JwtSecurityToken(
            issuer: "https://:45092/",
            audience: "https://:45092/",
            claims: GetUserClaims(user),
            notBefore: new DateTimeOffset(DateTime.Now).DateTime,
            expires: new DateTimeOffset(DateTime.Now.AddDays(1)).DateTime,
            //Using HS256 Algorithm to encrypt Token
            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), 
                                SecurityAlgorithms.HmacSha256Signature)
        );
        var token = new JwtSecurityTokenHandler().WriteToken(JWToken);
        return token;
    }
    else
    {
        return null;
    }
}    

需要考虑的一些要点...

在创建令牌时,我们需要提供与 Startup.csJWToken 配置相同的安全密钥。

var key = Encoding.ASCII.GetBytes
          ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv");

issuer” 和 “audience” 应该与 Startup.csConfigureServices() 方法中配置的值相同。

最后,TokenProvider.cs 类如下所示

using LoginDemo.Models;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace LoginDemo
{
    public class TokenProvider
    {
        public string LoginUser(string UserID, string Password)
        {
            //Get user details for the user who is trying to login
            var user = UserList.SingleOrDefault(x => x.USERID == UserID);
            
            //Authenticate User, Check if it’s a registered user in Database 
            if (user == null)
                return null;
                
            //If it is registered user, check user password stored in Database
            //For demo, password is not hashed. It is just a string comparision 
            //In reality, password would be hashed and stored in Database. 
            //Before comparing, hash the password again.
            if (Password == user.PASSWORD)
            {
                //Authentication successful, Issue Token with user credentials 
                //Provide the security key which is given in 
                //Startup.cs ConfigureServices() method 
                var key = Encoding.ASCII.GetBytes
                ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv"); 
                //Generate Token for user 
                var JWToken = new JwtSecurityToken( 
                    issuer: "https://:45092/",
                    audience: "https://:45092/",
                    claims: GetUserClaims(user),
                    notBefore: new DateTimeOffset(DateTime.Now).DateTime,
                    expires: new DateTimeOffset(DateTime.Now.AddDays(1)).DateTime,
                    //Using HS256 Algorithm to encrypt Token  
                    signingCredentials: new SigningCredentials
                    (new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                );
                var token = new JwtSecurityTokenHandler().WriteToken(JWToken);
                return token;
            }
            else
            {
                return null;
            }
        }
        
        //Using hard coded collection list as Data Store for demo. 
        //In reality, User details would come from Database.
        private List UserList = new List
        {
            new User { USERID = "jsmith@email.com", 
            PASSWORD = "test", EMAILID = "jsmith@email.com", 
            FIRST_NAME = "John", LAST_NAME = "Smith", 
            PHONE = "356-735-2748", ACCESS_LEVEL = "Director", 
            READ_ONLY = "true" },
            new User { USERID = "srob@email.com", PASSWORD = "test", 
            FIRST_NAME = "Steve", LAST_NAME = "Rob", 
            EMAILID = "srob@email.com", PHONE = "567-479-8537", 
            ACCESS_LEVEL = "Supervisor", READ_ONLY = "false" },
            new User { USERID = "dwill@email.com", PASSWORD = "test", 
            FIRST_NAME = "DJ", LAST_NAME = "Will", 
            EMAILID = "dwill@email.com", PHONE = "599-306-6010", 
            ACCESS_LEVEL = "Analyst", READ_ONLY = "false" },
            new User { USERID = "JBlack@email.com", PASSWORD = "test", 
            FIRST_NAME = "Joe", LAST_NAME = "Black", 
            EMAILID = "JBlack@email.com", PHONE = "764-460-8610", 
            ACCESS_LEVEL = "Analyst", READ_ONLY = "true" }
        };
        
        //Using hard coded collection list as Data Store for demo. 
        //In reality, User data comes from Database or other Data Source 
        private IEnumerable GetUserClaims(User user)
        {
            IEnumerable claims = new Claim[]
                    {
                    new Claim(ClaimTypes.Name, user.FIRST_NAME + " " + user.LAST_NAME),
                    new Claim("USERID", user.USERID),
                    new Claim("EMAILID", user.EMAILID),
                    new Claim("PHONE", user.PHONE),
                    new Claim("ACCESS_LEVEL", user.ACCESS_LEVEL.ToUpper()),
                    new Claim("READ_ONLY", user.READ_ONLY.ToUpper())
                    };
            return claims;
        }
    }
}    

令牌存储

既然我们已经对用户进行了身份验证并为该用户发放了令牌,我们就需要将此令牌存储在某个地方,直到用户从应用程序中注销。这是必需的,因为在成功登录后的每个后续 HTTP 请求中都需要传递该令牌。如上所述,我们不打算在客户端 (浏览器) 使用任何 Cookie 来存储令牌。

相反,我们将把令牌存储在服务器端的用户 SESSION 中。创建一个 SESSION 变量并将其存储在此处。成功登录后,对于每个后续请求,我们将从 session 变量中获取令牌并将其插入到传入的 HTTP 请求中。

我们将在下面的 HomeController 操作方法中执行此操作,从 TokenProvider.cs 获取令牌,创建一个名为 “JWToken” 的 Session 对象并存储令牌。

HomeController.cs 中,有一个 “LoginUser” 操作方法。用户将从 Index.cshtml 输入用户 ID 和密码,并将页面提交到 HomeController.cs 中的 “LoginUser” 操作方法。在 ‘LoginUser” 控制器操作方法中,我们将把令牌添加到名为 “JWToken” 的 session 对象中。

HttpContext.Session.SetString("JWToken", userToken);    

中间件

现在到了整个实现的关键部分。这部分更多的是概念和几行代码。我们将在这里做两件事:

  1. 将令牌插入 HTTP 请求
  2. 将用户声明加载到 HTTP 请求

让我们先理解概念。尽量保持简单,请耐心等待。

身份验证和授权通过 HTTP 请求处理,为此:

  • 令牌应该是 HTTP 请求的一部分,并且应该来自 HTTP 请求头。
  • ClaimsPrincipleClaimsIdentity (HttpContext.User.Identity) 对象是从当前的 HTTP Context 创建的。
  • 用户声明是从 HTTP 请求头读取并加载到 HTTP Claims 身份对象中的。
  • 换句话说,授权是通过传入的 HTTP 请求完成的,而不是直接从令牌读取。
  • 通过这样做,HTTP 请求本身就为该用户进行了授权。

为了实现上述目标:

  • 我们需要将 Token (存储在用户 session 变量 “JWToken” 中) 插入到每个传入的 HTTP 请求中。
  • Token 读取用户声明值,并将其加载到 HTTP Context Claims Principle 对象中。
  • 如果在 “JWToken” session 变量中找不到令牌,则 HTTP 请求头 “Authorization” 将为空。在这种情况下,该用户的 Claims Principle 将不会在 HTTP Context 中设置。这将拒绝用户的权限。

下面的图片展示了我们将如何将令牌插入 HTTP 头并设置 HTTP Context 中的 Claims Principle。

自定义中间件 app.Use()

创建自定义中间件的主要思想是将其插入到传入的 HTTP 请求中。现在我们已登录用户的令牌存储在 Session 变量 “JWToken” 中,我们需要将其插入到所有后续传入的 HTTP 请求中。为此,我们将编写几行代码到 ASP.NET Core 中间件。这只不过是 HTTP 管道。自定义中间件在 Startup.csConfigure() 方法中添加。

附注:Token 在用户登录时仅创建一次。

中间件 app.UseAuthentication()

现在我们需要验证令牌并将声明加载到 HTTP Request 上下文中。UseAuthentication() 会为我们完成这项工作。在 HTTP 请求到达 MVC 控制器之前,UseAuthentication() 会执行以下操作:

  • 使用 Startup.csConfigureServices() 方法中 AddJwtBearer() 配置中提供的密钥来解密和验证令牌。
  • 在 HTTP 请求上下文中设置 User 对象。
  • 最后,从 Token 读取 Claims 值并加载到 HttpContext.User.Identity 对象中。

自定义中间件代码

Startup.cs 中,将以下代码添加到 Configure() 方法中。在 app.UseCookiePolicy() 之后添加下面的代码。这里的代码执行顺序很重要。

        app.UseSession();
        //Add JWToken to all incoming HTTP Request Header
        app.Use(async (context, next) =>
        {
            var JWToken = context.Session.GetString(“JWToken”);
            if (!string.IsNullOrEmpty(JWToken))
            {
                context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
            }
            await next();
        });
        //Add JWToken Authentication service
        app.UseAuthentication();    

让我们逐步了解代码:

  • app.UseSession() 是使用 Session 对象的配置。
  • 要编写自定义中间件,请使用 app.Use()
  • 首先,我们需要 Token 才能将其插入 HTTP 请求。我们已将令牌存储在 Session 中。从 session 变量 “JWToken” 中获取 Token
    var JWToken = context.Session.GetString(“JWToken”);                
  • 下一行检查 Token 是否在 Session 中可用。如果不可用,则用户未通过身份验证。因此,拒绝用户权限。
  • 如果 Token 存在于 “JWTokenSession 变量中,则说明用户已通过身份验证。
  • 现在,我们需要将 Token 添加到 HTTP Request 中 (记住,用户身份是通过 HTTP Request 创建的)。下面的代码将令牌添加到所有传入的 HTTP Request 中。
     context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
                    
  • 请注意,我们将 Token 添加到 HTTP Request 的 “Authorization” 头中。是的,将令牌添加到 “Authorization” 头非常重要,并且令牌应与关键字 “Bearer ” 连接。
  • 下一行代码是 app.UseAuthentication()
    1. 此行代码将查找在 ConfigureServices() 方法中配置的身份验证机制。在我们的 ConfigureService() 中,我们使用了 “AddJwtBearer” 配置,它是 Microsoft.AspNetCore.Authentication 命名空间的一部分。
    2. AddJWtBeared() 内部,我们有带有密钥、过期日期等的令牌配置。
    3. 当 HTTP Request 进来时,app.UseAuthentication() 将在 HTTP Request 中查找 “Authorization” 头。它将读取存储在 “Authorization” 头中的值,并将其传递给 Microsoft.AspNetCore.AuthenticationMicrosoft.AspNetCore.Authentication 将根据我们为令牌设置的配置来评估和验证令牌。这包括使用我们在配置中提供的密钥解密令牌,并从令牌读取声明,然后将声明加载到 HttpContext.User.Identity 对象中。此时,HTTP Context 本身就已通过身份验证和授权。
    4. 这个完整的执行仅对一个 HTTP Request (那个特定的传入请求) 有效。我们需要为所有后续 HTTP Request 执行此操作。这就是为什么我们将 Token 存储在 session 变量中,并将 Token 分配给所有后续传入 HTTP Request 的 HTTP RequestAuthorization” 头。所有传入的 HTTP Request 和传出的 HTTP Response 都通过 Startup.cs Configure() 方法中的 HTTP 管道。

最后,Startup.cs Configure() 方法如下所示:

        // This method gets called by the runtime. 
        // Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();
            app.UseCookiePolicy();

            //Add User session
            app.UseSession();

            //Add JWToken to all incoming HTTP Request Header
            app.Use(async (context, next) =>
            {
                var JWToken = context.Session.GetString("JWToken");
                if (!string.IsNullOrEmpty(JWToken))
                {
                    context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
                }
                await next();
            });
            //Add JWToken Authentication service
            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }    

登录页面 (Index.cshtml)

现在让我们创建一个简单的登录页面 (Index.cshtml),其中包含用户 ID 和密码文本框。向视图页面添加 User.cs 模型。在这里,您可以看到 IF 条件 User.Identity.IsAuthenticated,它检查用户是否已通过身份验证。 “User” 对象是 System.Security.Claims 的一部分,它由中间件在 HTTP Context 中设置。如果用户已通过身份验证,我们将显示来自 claims 身份名称属性的用户姓名。否则,我们将要求用户登录。

        @model LoginDemo.Models.User
        @{
            ViewData["Title"] = "Home Page";
        }
        <div style="padding-top:50px;"></div>
        <div style="padding-top:50px;">
            @if (User.Identity.IsAuthenticated)
            {
                <div class="row">            
                    You are Logged in as 
                    <span style="font-size:large;color:forestgreen;">
                    @User.Identity.Name</span>
                </div>
                <div class="row" style="padding-top:50px;">
                    @Html.ActionLink("Log Off", "Logoff", 
                    "Home", null, new { @class = "btn btn-primary btn-lg rph-login-button" })
                </div>
            }
            else
            {
                <div class="row">                        
                    <div class="col-lg-4 col-md-4 col-sm-4">
                        <div> 
                            @using (Html.BeginForm("LoginUser", "Home", 
                            FormMethod.Post, new { role = "form" }))
                            { 
                                <div>  
                                    @Html.AntiForgeryToken()      
                                    <div>  
                                        <label>User ID</label><br />
                                    </div>
                                    <div>  
                                        @Html.TextBoxFor(m => m.USERID, 
                                        new {@class = "form-control txtbox"})
                                    </div>
                                    <div style="padding-top:20px;"></div>
                                    <div>  
                                        <label>Password</label><br />
                                    </div>
                                    <div>  
                                        @Html.PasswordFor(m => m.USERID, 
                                        new {@class = "form-control txtbox"})
                                    </div>
                                </div>
                                <div class="padding-left:35%;width:40%;">        
                                    <div class="padding-top:20px;">        
                                        <input class="btn btn-primary 
                                        btn-lg rph-login-button" 
                                        type="submit" value="Login"/>        
                                    </div>
                                </div>
                            }
                        </div>
                    </div>
                    <div class="col-lg-8 col-md-8 col-sm-8">                        
                        <div style="padding-top:50px;">
                            <div><b>Please login with any of the below User ID, 
                            Password is span style="font-size:large;color:forestgreen;"
                            >test</span> for all Users</b></div>  
                            <div style="padding-top:10px;">
                                <ui style="list-style: none;">
                                    <li>jsmith@email.com  -  Director, Read Only - true</li>
                                    <li>srob@email.com  -  Supervisor, Read Only - false</li>
                                    <li>dwill@email.com  -  Analyst, Read Only - false</li>
                                    <li>JBlack@email.com  -  Analyst, Read Only - true</li>
                                </ui>
                            </div>
                        </div>
                    </div>
                </div>
            }
        </div>    

Home Controller

让我们在 HomeController.cs 中添加两个 Action 方法。一个用于 Index (登录) 页面,另一个用于提交登录页面。

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult LoginUser(User user)
        {
            TokenProvider _tokenProvider = new TokenProvider();
            //Authenticate user
            var userToken = _tokenProvider.LoginUser(user.USERID.Trim(), user.PASSWORD.Trim());
            if (userToken != null)
            {
                //Save token in session object 
                HttpContext.Session.SetString("JWToken", userToken);
            }
            return Redirect("~/Home/Index");
        }    

Action 方法 LoginUser(User user) 从登录页面获取用户 ID 和密码值。下一行通过检查数据存储中的用户 ID 和密码来执行身份验证。

        var userToken = _tokenProvider.LoginUser(user.USERID.Trim(), user.PASSWORD.Trim());    

接下来的几行检查 TokenProvider() 是否颁发了令牌。如果有,则将令牌保存在用户 Session 变量 “JWToken” 中。

        if (userToken != null)
        {
            //Save token in session object
            HttpContext.Session.SetString("JWToken", userToken);
        }    

然后,将页面重定向到 Index.cshtml

        return Redirect("~/Home/Index");    

在页面重定向期间,我们已经将令牌存储在 session 对象中。现在页面重定向将通过 Startup.cs 中的 HTTP 管道。现在自定义中间件将停止 HTTP Request 并将令牌插入 HTTP Request 头 “Authorization”。有关更多详细信息,请参阅“中间件”。

如果在 “JWTokensession 变量中找不到 token,则 HTTP Request 头 “Authorization” 将为空。在这种情况下,将不会为该用户设置 HTTP Context。重定向将要求用户登录。

注销

让我们注销用户。当没有令牌时,HTTP Context 就无法为用户设置。因此,从 session 对象中删除 token。要从 session 中删除 token,请清除用户的 session重定向到另一个控制器操作

添加一个控制器操作方法 Logoff()。清除用户的 session 并重定向到 Index 操作方法。重定向到另一个控制器操作方法很重要。让我们看看原因?例如,在 Logoff() 操作方法中,我们返回一个 View() 而不是 Redirect()。在这种情况下,视图页面将呈现给浏览器,用户仍然可以访问该页面,User.Identity.IsAuthenticated 仍然是 true。当 ASP.NET 执行控制器操作方法时,它处于 HTTP RESPONSE 的处理过程中。这意味着它已经通过了 HTTP REQUEST。用户 Claims Principle 在 HTTP Request 中设置。通过注销用户,我们也需要清除该用户的 Claims Principle。仅仅清除 session 是不够的。因此,我们需要再次通过 HTTP 管道。重定向到另一个控制器会经过 HTTP 管道,它将在 “JWTokensession 变量中查找 Token。但我们已经清除了 sessiontoken 不再在 session 中。没有令牌,Claims Principle 就无法在 HTTP Context 中设置。这将完全注销用户。

        public IActionResult Logoff()
        {
            HttpContext.Session.Clear();
            return Redirect("~/Home/Index");
        }    

Controller 代码

        using System;
        using System.Collections.Generic;
        using System.Diagnostics;
        using System.Linq;
        using System.Threading.Tasks;
        using Microsoft.AspNetCore.Mvc;
        using LoginDemo.Models;
        using System.Security.Claims;
        using Microsoft.AspNetCore.Http;

        namespace LoginDemo.Controllers
        {
            public class HomeController : Controller
            {
                public IActionResult Index()
                {
                    return View();
                }

                public IActionResult LoginUser(User user)
                {
                    TokenProvider _tokenProvider = new TokenProvider();
                    var userToken = _tokenProvider.LoginUser(user.USERID.Trim(), 
                                    user.PASSWORD.Trim());
                    if (userToken != null)
                    {
                        //Save token in session object
                        HttpContext.Session.SetString("JWToken", userToken);
                    }
                    return Redirect("~/Home/Index");
                }

                public IActionResult Logoff()
                {
                    HttpContext.Session.Clear();
                    return Redirect("~/Home/Index");
                }
            }
        }    

登录演示项目

登录页面

LoginDemo.sln

第二部分

在第 2 部分,我们将介绍用户授权。我们将看到:

  1. 如何为用户提供页面级别访问权限
  2. 如何创建自定义授权过滤器属性来限制控制器级别和操作方法级别的用户
  3. 用自定义授权属性装饰控制器操作方法
  4. 限制用户在未登录的情况下直接访问页面

转到第 2 部分

© . All rights reserved.