ASP.Net Core 3.1 中使用JWT認(rèn)證

JWT認(rèn)證簡單介紹

關(guān)于Jwt的介紹網(wǎng)上很多,此處不在贅述敬扛,我們主要看看jwt的結(jié)構(gòu)晰洒。

JWT主要由三部分組成,如下:

HEADER.PAYLOAD.SIGNATURE

HEADER包含token的元數(shù)據(jù)舔哪,主要是加密算法欢顷,和簽名的類型,如下面的信息捉蚤,說明了

加密的對象類型是JWT,加密算法是HMAC SHA-256

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

然后需要通過BASE64編碼后存入token中

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9    

Payload主要包含一些聲明信息(claim)炼七,這些聲明是key-value對的數(shù)據(jù)結(jié)構(gòu)缆巧。

通常如用戶名,角色等信息豌拙,過期日期等陕悬,因?yàn)槭俏醇用艿模圆唤ㄗh存放敏感信息按傅。

{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"admin","exp":1578645536,"iss":"webapi.cn","aud":"WebApi"}

也需要通過BASE64編碼后存入token中

eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9 

Signaturejwt要符合jws(Json Web Signature)的標(biāo)準(zhǔn)生成一個最終的簽名捉超。把編碼后的Header和Payload信息加在一起,然后使用一個強(qiáng)加密算法唯绍,如 HmacSHA256拼岳,進(jìn)行加密。HS256(BASE64(Header).Base64(Payload)况芒,secret)

2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

最后生成的token如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

開發(fā)環(huán)境

框架:asp.net 3.1

IDE:VS2019

ASP.NET 3.1 Webapi中使用JWT認(rèn)證

命令行中執(zhí)行執(zhí)行以下命令惜纸,創(chuàng)建webapix項(xiàng)目:

dotnet new webapi -n Webapi -o WebApi

特別注意的時,3.x默認(rèn)是沒有jwt的Microsoft.AspNetCore.Authentication.JwtBearer庫的绝骚,所以需要手動添加NuGet Package耐版,切換到項(xiàng)目所在目錄,執(zhí)行 .net cli命令

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.1.0

創(chuàng)建一個簡單的POCO類压汪,用來存儲簽發(fā)或者驗(yàn)證jwt時用到的信息

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Webapi.Models

{
    public class TokenManagement
    {
        [JsonProperty("secret")]
        public string Secret { get; set; }

        [JsonProperty("issuer")]
        public string Issuer { get; set; }

        [JsonProperty("audience")]
        public string Audience { get; set; }

        [JsonProperty("accessExpiration")]
        public int AccessExpiration { get; set; }

        [JsonProperty("refreshExpiration")]
        public int RefreshExpiration { get; set; }
    }
}

然后在 appsettings.Development.json 增加jwt使用到的配置信息(如果是生成環(huán)境在appsettings.json添加即可)

"tokenManagement": {
        "secret": "123456",
        "issuer": "webapi.cn",
        "audience": "WebApi",
        "accessExpiration": 30,
        "refreshExpiration": 60
    }

然后再startup類的ConfigureServices方法中增加讀取配置信息

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
            var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();

        }

到目前為止粪牲,我們完成了一些基礎(chǔ)工作,下面再webapi中注入jwt的驗(yàn)證服務(wù)止剖,并在中間件管道中啟用authentication中間件腺阳。

startup類中要引用jwt驗(yàn)證服務(wù)的命名空間

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

然后在ConfigureServices方法中添加如下邏輯

services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
                    ValidIssuer = token.Issuer,
                    ValidAudience = token.Audience,
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });

Configure方法中啟用驗(yàn)證

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseAuthentication();
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

上面完成了JWT驗(yàn)證的功能落君,下面就需要增加簽發(fā)token的邏輯。我們需要增加一個專門用來用戶認(rèn)證和簽發(fā)token的控制器舌狗,命名成AuthenticationController叽奥,同時增加一個請求的DTO類

public class LoginRequestDTO
    {
        [Required]
        [JsonProperty("username")]
        public string Username { get; set; }


        [Required]
        [JsonProperty("password")]
        public string Password { get; set; }
    }
[Route("api/[controller]")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
        [AllowAnonymous]
         [HttpPost, Route("requestToken")]
        public ActionResult RequestToken([FromBody] LoginRequestDTO request)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest("Invalid Request");
            }

            return Ok();

        }
    }

目前上面的控制器只實(shí)現(xiàn)了基本的邏輯,下面我們要創(chuàng)建簽發(fā)token的服務(wù)痛侍,去完成具體的業(yè)務(wù)朝氓。第一步我們先創(chuàng)建對應(yīng)的服務(wù)接口,命名為IAuthenticateService

public interface IAuthenticateService
    {
        bool IsAuthenticated(LoginRequestDTO request, out string token);
    }

接下來主届,實(shí)現(xiàn)接口

public class TokenAuthenticationService : IAuthenticateService
    {
        public bool IsAuthenticated(LoginRequestDTO request, out string token)
        {
            throw new NotImplementedException();
        }
    }

StartupConfigureServices方法中注冊服務(wù)

services.AddScoped<IAuthenticateService, TokenAuthenticationService>();

在Controller中注入IAuthenticateService服務(wù)赵哲,并完善action

public class AuthenticationController : ControllerBase
    {
        private readonly IAuthenticateService _authService;
        public AuthenticationController(IAuthenticateService authService)
        {
            this._authService = authService;
        }
        [AllowAnonymous]
         [HttpPost, Route("requestToken")]
        public ActionResult RequestToken([FromBody] LoginRequestDTO request)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest("Invalid Request");
            }

            string token;
            if (_authService.IsAuthenticated(request, out token))
            {
                return Ok(token);
            }

            return BadRequest("Invalid Request");

        }
    }

正常情況,我們都會根據(jù)請求的用戶和密碼去驗(yàn)證用戶是否合法君丁,需要連接到數(shù)據(jù)庫獲取數(shù)據(jù)進(jìn)行校驗(yàn)枫夺,我們這里為了方便,假設(shè)任何請求的用戶都是合法的绘闷。

這里單獨(dú)加個用戶管理的服務(wù)橡庞,不在IAuthenticateService這個服務(wù)里面添加相應(yīng)邏輯,主要遵循了職責(zé)單一原則印蔗。首先和上面一樣扒最,創(chuàng)建一個服務(wù)接口IUserService

public interface IUserService
    {
        bool IsValid(LoginRequestDTO req);
    }

實(shí)現(xiàn)IUserService接口

public class UserService : IUserService
    {
        //模擬測試,默認(rèn)都是人為驗(yàn)證有效
        public bool IsValid(LoginRequestDTO req)
        {
            return true;
        }
    }

同樣注冊到容器中

services.AddScoped<IUserService, UserService>();

接下來华嘹,就要完善TokenAuthenticationService簽發(fā)token的邏輯吧趣,首先要注入IUserService 和 TokenManagement,然后實(shí)現(xiàn)具體的業(yè)務(wù)邏輯耙厚,這個token的生成還是使用的Jwt的類庫提供的api强挫,具體不詳細(xì)描述。

特別注意下TokenManagement的注入是已IOptions的接口類型注入的薛躬,還記得在Startpup中嗎俯渤?我們是通過配置項(xiàng)的方式注冊TokenManagement類型的。

 public class TokenAuthenticationService : IAuthenticateService
    {
        private readonly IUserService _userService;
        private readonly TokenManagement _tokenManagement;
        public TokenAuthenticationService(IUserService userService, IOptions<TokenManagement> tokenManagement)
        {
            _userService = userService;
            _tokenManagement = tokenManagement.Value;
        }
        public bool IsAuthenticated(LoginRequestDTO request, out string token)
        {
            token = string.Empty;
            if (!_userService.IsValid(request))
                return false;
            var claims = new[]
            {
                new Claim(ClaimTypes.Name,request.Username)
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials);

            token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

            return true;

        }
    }

準(zhǔn)備好測試試用的APi泛豪,打上Authorize特性稠诲,表明需要授權(quán)!

[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]
        public IEnumerable<WeatherForecast> Get()
        {
            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();
        }
    }

支持我們可以測試驗(yàn)證了诡曙,我們可以使用postman來進(jìn)行http請求臀叙,先啟動http服務(wù),獲取url价卤,先測試一個訪問需要授權(quán)的接口劝萤,但沒有攜帶token信息,返回是401慎璧,表示未授權(quán)

image

下面我們先通過認(rèn)證接口床嫌,獲取token跨释,居然報錯,查詢了下厌处,發(fā)現(xiàn)HS256算法的秘鑰長度最新為128位鳖谈,轉(zhuǎn)換成字符至少16字符,之前設(shè)置的秘鑰是123456阔涉,所以導(dǎo)致異常缆娃。

System.ArgumentOutOfRangeException: IDX10603: Decryption failed. Keys tried: 'HS256'. Exceptions caught: '128'. token: '48' (Parameter 'KeySize') at

更新秘鑰

 "tokenManagement": {
        "secret": "123456123456123456",
        "issuer": "webapi.cn",
        "audience": "WebApi",
        "accessExpiration": 30,
        "refreshExpiration": 60
    }

重新發(fā)起請求,成功獲取token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDUyMDMsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.AehD8WTAnEtklof2OJsvg0U4_o8_SjdxmwUjzAiuI-o

image

把token帶到之前請求的api中瑰排,重新測試贯要,成功獲取數(shù)據(jù)


image

總結(jié)

基于token的認(rèn)證方式,讓我們構(gòu)建分布式/松耦合的系統(tǒng)更加容易椭住。任何地方生成的token崇渗,只有擁有相同秘鑰,就可以再任何地方進(jìn)行簽名校驗(yàn)京郑。

當(dāng)然要用好jwt認(rèn)證方式宅广,還有其他安全細(xì)節(jié)需要處理,比如palyload中不能存放敏感信息些举,使用https的加密傳輸方式等等乘碑,可以根據(jù)業(yè)務(wù)實(shí)際需要再進(jìn)一步安全加固!

同時我們也發(fā)現(xiàn)使用token金拒,就可以擺脫cookie的限制,所以JWT是移動app開發(fā)的首選套腹!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绪抛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子电禀,更是在濱河造成了極大的恐慌幢码,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尖飞,死亡現(xiàn)場離奇詭異症副,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)政基,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門贞铣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沮明,你說我怎么就攤上這事辕坝。” “怎么了荐健?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵酱畅,是天一觀的道長琳袄。 經(jīng)常有香客問我,道長纺酸,這世上最難降的妖魔是什么窖逗? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮餐蔬,結(jié)果婚禮上碎紊,老公的妹妹穿的比我還像新娘。我一直安慰自己用含,他們只是感情好矮慕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啄骇,像睡著了一般痴鳄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缸夹,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天痪寻,我揣著相機(jī)與錄音,去河邊找鬼虽惭。 笑死橡类,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芽唇。 我是一名探鬼主播顾画,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼匆笤!你這毒婦竟也來了研侣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炮捧,失蹤者是張志新(化名)和其女友劉穎庶诡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咆课,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡末誓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了书蚪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喇澡。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖善炫,靈堂內(nèi)的尸體忽然破棺而出撩幽,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布窜醉,位于F島的核電站宪萄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榨惰。R本人自食惡果不足惜拜英,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望琅催。 院中可真熱鬧居凶,春花似錦、人聲如沸藤抡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缠黍。三九已至弄兜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓷式,已是汗流浹背替饿。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贸典,地道東北人视卢。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像廊驼,于是被迫代替她去往敵國和親据过。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容