從0到1詳解Jwt以及在Asp.NetCore 3.1中使用JWT Bearer授權認證

編輯于 2020/03/15
修改于 2020/04/02

環境 Asp. Net Core WebAPI 3.1

JWT介紹


jwt全稱JSON Web Token,是目前最流行的跨域授權認證解決方案。
jwt是一個記錄著用戶身份信息的令牌,訪問api時持有這個令牌就可以訪問api,這就是jwt。
JWT官網

  • 授權:當用戶登陸成功時,返回jwt的Token,后續的每次請求都將攜帶Jwt發放的token,來實現認證并返回所請求的數據。
    </br>
  • 認證:因JWT簽名使用公鑰私鑰對,只要私鑰不丟失,就可以確認發放token的人,而且使用了計算簽名,可以有效的判斷驗證內容是否被修改。

JWT Token結構


HEADER.PAYLOAD.SIGNATURE
  • HEADER
  • PAYLOAD 載荷
  • SIGNATURE 簽名
  1. HEADER 頭
    HEADER包含token的元數據,使用的加密算法,和簽名的類型,如下所示,加密類型是JWT,算法是HMAC SHA-256

    {"alg":"HS256","typ":"JWT"}
    

    再通過base64算法編碼,形成JWT頭
    </br>

  2. PAYLOAD 載荷
    PAYLOAD也是一個Json,用來存放需要傳遞的數據,JWT官方解釋了7個字段可以使用。

    • iss (issuer):簽發人
    • exp (expiration time):過期時間
    • sub (subject):主題
    • aud (audience):受眾
    • nbf (Not Before):生效時間
    • iat (Issued At):簽發時間
    • jti (JWT ID):編號

    同時也可以自定義添加字段,如

    {
        "Account": "Tuser",
        "Name": "我叫Tuser",
        "Age": 18
    }
    

    需要注意的是,PAYLOAD包含的字段是通過Base64編碼的,所以任何獲取到token的人都可以讀取到里面的內容
    </br>

  3. SIGNATURE 簽名
    SIGNATURE 部分是對前兩部分的簽名,防止數據篡改。
    首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。

    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)
    

    下面顯示了一個JWT,它具有先前的頭和??有效負載編碼,并使用機密簽名。
    JWT官網提供了解碼器,大家可以自行嘗試

    JWT內容

JWT運行詳解


一般在身份驗證中用戶請求登陸成功后,后端將返回JWT Token給前端,其中包含了用戶的部分可見信息。
當前端請求受到請求限制的資源時,前端在請求頭中帶著JWT Token進行請求,如Authorization: Bearer <token>
</br>
在請求受保護的路由將檢查Authorization標頭中的有效JWT,如果JWT包含必要的數據,則可減能可以少查詢數據庫以進行某些操作的需要。
</br>
使用Jwt身份驗證,那么跨域資源共享將不會成為問題,因為它不使用cookie。
</br>

Asp .Net WebAPI中使用Jwt


一. 創建. Net Core WebAPI 3.1項目并安裝NuGet包+前置工作

  1. 創建項目
    這里就不演示了
    </br>

  2. 安裝的NuGet包

    Jwt 包

    也可以通過進行雙擊項目在包還原下添加,不明白的可以博主最后面提供的源碼
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.2" />
    </br>

  3. 創建Utils工具類庫

    • 創建. net core 類庫并命名為Utils
    • 創建Helper文件夾并添加Appsettings.cs文件
    • 添加Appsettings代碼
      Appsettings.cs
        using Microsoft.Extensions.Configuration;
        using Microsoft.Extensions. Configuration.Json;
    
        namespace NetCore.Blogs.Demo.Utils
        {
            /// <summary>
            /// appsettings.json操作類
            /// </summary>
            public class Appsettings
            {
                static IConfiguration Configuration { get; set; }
                static string contentPath { get; set; }
    
                public Appsettings(string contentPath)
                {
                    string Path = "appsettings.json";
    
                    //如果你把配置文件 是 根據環境變量來分開了,可以這樣寫
                    //Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json";
    
                    Configuration = new ConfigurationBuilder()
                    .SetBasePath(contentPath)
                    .Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//這樣的話,可以直接讀目錄里的json文件,而不是 bin 文件夾下的,所以不用修改復制屬性
                    .Build();
    
    
                }
    
                /// <summary>
                /// 封裝要操作的字符
                /// </summary>
                /// <param name="sections">節點配置</param>
                /// <returns></returns>
                public static string app(params string[] sections)
                {
                    try
                    {
    
                        if (sections.Any())
                        {
                            return Configuration[string.Join(":", sections)];
                        }
                    }
                    catch (Exception) { }
    
                    return "";
                }
            }
        }
    
    • 創建完后Utils類庫結構如下


      Utils結構
    • 最后在Startup.cs中注入Appsettings類


      注入cs中注入Appsettings類

      完成后,就可以通過Appsettings.app(string[]) 傳入一個數組進行訪問appsettings.json配置文件了

二. 創建Jwt授權認證

重頭戲來了!

  1. appsettings.json中添加Jwt字段

    • Issuer(發行人)
    • Audience(受眾)
    • SecurityKey(秘鑰)
    "JwtSettings": {
        "Issuer": "zyknow",
        "Audience": "audience",
        "SecurityKey": "zyknowzyknowzyknowzyknowzyknowzyknow"
    }
    

    </br>

  2. 在webApi項目中創建Extensions文件夾并創建AuthorizationSetup.cs類,這樣把在Startup.cs做的操作提出來,這樣Startup.cs看起來更簡潔些,如圖所示,并且粘貼代碼

    Extensions結構

    AuthorizationSetup.cs

    public static class AuthorizationSetup
    {
        public static void AddAuthorizationSetup(this IServiceCollection services)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
    
            // 配置策略授權
            services.AddAuthorization(o =>
            {
                // 添加策略,使用時在方法上標注[Authorize(Policy ="AdminPolicy")],就會驗證請求token中的ClaimTypes.Role是否包含了Admin
                o.AddPolicy("AdminPolicy", o =>
                {
                    //ClaimTypes.Role == Admin
                    o.RequireRole("Admin").Build();
    
                    //ClaimTypes.Role == Admin 或者 == User
                    //o.RequireRole("Admin","User").Build(); 
    
                    //ClaimTypes.Role == Admin 并且 == User ,關于添加多個角色策略,在Login控制器中
                    //o.RequireRole("Admin").RequireRole("User").Build(); 
                });
    
                //只有User的策略
                o.AddPolicy("onlyUserPolicy", o =>
                {
                    o.RequireRole("User").Build();
                });
    
                //User和Admin都可以訪問的策略
                o.AddPolicy("UserOrAdminPolicy", o =>
                {
                    o.RequireRole("User", "Admin").Build();
                });
    
                //User并且是Admin才能請求的策略
                o.AddPolicy("UserAndAdminPolicy", o =>
                {
                    o.RequireRole("User").RequireRole("Admin").Build();
                });
            });
    
            string key = Appsettings.app(new string[] { "JwtSettings", "SecurityKey" });
            string issuer = Appsettings.app(new string[] { "JwtSettings", "Issuer" });
            string audience = Appsettings.app(new string[] { "JwtSettings", "Audience" });
    
            SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)); //秘鑰的長度有要求,必須>=16位
    
            services.AddAuthentication("Bearer").AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters()
                {
                    //是否秘鑰認證
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = securityKey, //秘鑰
    
                    //是否驗證發行人
                    ValidateIssuer = true,
                    ValidIssuer = issuer, //這個字符串可以隨便寫,就是發行人
    
                    //是否驗證訂閱
                    ValidateAudience = true,
                    ValidAudience = audience,
    
                    //是否驗證過期時間
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                };
            });
        }
    }
    
    

    </br>

  3. 注冊Jwt到Startup.cs中


    ConfigureServices
    Configure

    </br>

  4. 創建Login控制器并創建GetToken方法


    創建Login控制器

    這個控制器是用來發放Jwt Token的,通過把請求中攜帶的userRole添加到Jwt授權中并返回(實際應該是查詢數據庫,然后返回對應的權限等級之類的,比如admin,SuperAdmin,User等)

    注意一下代碼中自定義的Jwt字段,后面會提到

    [Route("api/[controller]")]
    [ApiController]
    [AllowAnonymous]
    public class LoginController : ControllerBase
    {
        /// <summary>
        /// 頒發令牌接口
        /// </summary>
        /// <returns></returns>
        [HttpGet(nameof(GetJwtToken))]
        public string GetJwtToken(string userRole)
        {
            //獲取在配置文件中獲取Jwt屬性設置
            string key = Appsettings.app(new string[] { "JwtSettings", "securityKey" });
            string issuer = Appsettings.app(new string[] { "JwtSettings", "Issuer" });
            string audience = Appsettings.app(new string[] { "JwtSettings", "Audience" });
    
            //創建授權的token類
            SecurityToken securityToken = new JwtSecurityToken(
                issuer: issuer, //簽發人
                audience: audience, //受眾
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)), SecurityAlgorithms.HmacSha256), //秘鑰
    
                //創建過期時間
                expires: DateTime.Now.AddHours(1), //過期時間 一小時之后過期
    
                //自定義JWT字段
                claims: new Claim[] {
                    new Claim(ClaimTypes.Role,userRole), //把模擬請求的角色權限添加到Role中
                    //下面的都是自定義字段,可以任意添加到Claim作為信息共享
                    new Claim("Name","我叫Tuser"),
                    new Claim("Age","18"),
                }
                );
    
            //返回請求token
            return JsonConvert.SerializeObject(new {token = new JwtSecurityTokenHandler().WriteToken(securityToken)});
        }
    }
    

    </br>

  5. 開啟調試,看看是否能正確請求到Token
    這里給大家推薦一下國人開發的谷歌瀏覽器調試插件,功能跟Postman一樣,非常便攜還是中文apizza

    進入正題,使用Http請求工具進行請求Login/GetJwtToken方法


    請求GetJwtToken成功

    可以看到成功的獲取到了Token

    我們可以復制token在Jwt官網中解析看看

    Jwt官網解析Token

    接下來輸入我們在appsettings.json中的SecurityKey字段


    SecurityKey字段內容
    有效令牌

    效驗成功了!有效令牌!
    通過在PAYLOAD定義的公共屬性(若沒有私鑰偽造令牌),可以很好的實現跨域信息共享
    </br>

  6. 在控制器中使用Jwt授權認證
    在創建項目時自動生成的WeatherForecastController.cs控制器中添加如下代碼

    [ApiController]
    [Route("[controller]")]
    [Authorize]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };
    
        private readonly ILogger<WeatherForecastController> _logger;
    
        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }
    
        [HttpGet]
        [Route(nameof(AllGet))]
        [AllowAnonymous] //沒有token也能訪問
        public IEnumerable<WeatherForecast> AllGet()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    
        [HttpGet]
        [Route(nameof(AllTokenGet))]
        [Authorize]   //只需要有效token
        public IEnumerable<WeatherForecast> AllTokenGet()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    
        [HttpGet]
        [Route(nameof(AdminPolicy))]
        [Authorize(Policy = "AdminPolicy")] //需要請求攜帶token的策略為Admin才能訪問
        public IEnumerable<WeatherForecast> AdminPolicy()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
        [HttpGet]
        [Route(nameof(onlyUserPolicy))]
        [Authorize(Policy = "onlyUserPolicy")] //需要請求攜帶token的策略為Admin才能訪問
        public IEnumerable<WeatherForecast> onlyUserPolicy()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    
        [HttpGet]
        [Route(nameof(AdminRequireMent))]
        [Authorize(Policy = "AdminRequireMent")] //自定義授權
        public IEnumerable<WeatherForecast> AdminRequireMent()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
    

    </br>

  7. 開啟調試
    這里就不一個個方法給大家演示了,有興趣的同學可以自己下載demo進行演示
    這里只給大家演示一下成功示例

    • 不攜帶Token請求,無法請求


      不攜帶token的請求
    • 在請求頭中攜帶token時請注意格式如下!


      Bearer認證格式

      攜帶沒有權限的token請求
      先請求login獲取token,注意看,這里使用的User


      注意看,這里使用的User

      復制token,再請求AdminPolicy接口,可以發現狀態碼為403,請求成功,但是權限錯誤,所以請求不到數據
      請求成功,但是權限錯誤
    • 最后,使用Admin生成的token來請求


      請求成功

自定義授權處理

這部分主要是講解Jwt授權認證的自定義授權處理環節

  1. 創建PolicyRequirement文件夾并創建AdminRequirement.cs類和MustRoleAdminHandler.cs

    </br>

    AdminRequirement.cs

    public class AdminRequirement : IAuthorizationRequirement
    {
        //這里定義的字段是在Login GetToken方法授權時確定的
        public string Name { get; set; }
    }  
    

    </br>

    MustRoleAdminHandler.cs

    //繼承AuthorizationHandler<AdminRequirement>
    public class MustRoleAdminHandler : AuthorizationHandler<AdminRequirement>
    {
        //重寫授權認證方法,每次jwt認證都會進入這個方法,可以開啟斷點調試查看
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
        {
            //這里獲取的是請求時AdminRequirement類中在jwt包含的字段
            Console.WriteLine(requirement);
    
            context.Succeed(requirement);
            return Task.CompletedTask;
        }
    }
    

    </br>

  2. AuthorizationSetup.cs中添加如下代碼

    添加代碼位置

     // 基于自定義處理策略
    o.AddPolicy("AdminRequireMent", o => {
        o.Requirements.Add(new AdminRequirement() { Name = "zyknow" });//完全自定義
    });  
    
    //依賴注入自定義處理策略
    services.AddSingleton<IAuthorizationHandler, MustRoleAdminHandler>();
    
    

    </br>

  3. WeatherForecastController.cs中添加使用自定義授權的方法

    自定義授權方法

    [HttpGet(nameof(AdminRequireMent))]
    [Authorize(Policy = "AdminRequireMent")] //自定義授權
    public IEnumerable<WeatherForecast> AdminRequireMent()
    {
        var rng = new Random();
    
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
    

    </br>

  4. 接著啟動程序,斷點調試,帶Jwt Token請求方法


    發送請求

    進入斷點,可以獲取到自定義授權策略的類,這里直接返回成功了,主要是只是為了演示,在這里可以做自己的邏輯處理


    進入斷點

源碼

下載Jwt分支即可

碼云

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 能不能遵行神的旨意,是信徒能不能真正認識神的分界線。屬靈的知識必須與屬靈的實踐相結合,化知識于行動,照著神的話語去...
    藍色紅寶石閱讀 3,253評論 0 1
  • 現在市面上環氧樹脂和水性聚氨酯兩種地坪是很多人的選擇,很多人會在這兩種地平之間搖擺不定,不知道選擇哪一種地坪,下面...
    老嚶捉小稽閱讀 1,282評論 0 0
  • 假裝會有很多人看到的樣子寫下一些關于考研的心得。沒錯,你猜得沒錯,我不是17的考生,我是18考研的菜鳥。 今天是早...
    謝大胃閱讀 338評論 15 5
  • 智者的聰慧和長者的經驗也許能夠通過語錄得以保存 許多人并不知道查理本人是個天才,而且他對奧林的投資哲學產生了深刻的...
    能量女神_2368閱讀 301評論 0 0
  • 本來昨晚是想等妹妹睡著了寫日記的,結果媽媽也睡著了,這不,一覺睡到現在,實不相瞞媽媽的心里也是有點小糾結,...
    王童杰媽媽閱讀 406評論 1 1