如何在.net6webapi中設定Jwt實現鑑權驗證

2023-06-02 12:00:21

JWT(Json Web Token)

jwt是一種用於身份驗證的開放標準,他可以在網路之間傳遞資訊,jwt由三部分組成:頭部,載荷,簽名。頭部包含了令牌的型別和加密演演算法,載荷包含了使用者的資訊,簽名則是對頭部和載荷的加密結果。

jwt鑑權驗證是指在使用者登入成功後,伺服器生成一個jwt令牌並返回給使用者端,使用者端在後續的請求中攜帶該令牌,服務通過令牌的簽名來確定使用者的身份和許可權。這種方式可以避免在每個請求中都需要進行身份驗證,提高了系統的效能和安全性。

jwt具有以下優點:

1.無狀態:jwt令牌包含了所有必要的資訊,伺服器不需要再每個請求中都進行身份驗證,避免了伺服器儲存對談資訊的開銷。

2.可延伸性:jwt令牌可以包含任意的資訊,可以根據需要新增自定義的欄位。

3.安全性:jwt令牌使用簽名來保證資料的完整性和真實性,防止資料被篡改或偽造。

4.跨平臺:jwt令牌是基於json格式的,可以再不同的變成語言和平臺之間進行傳遞和解析。

如何在webapi中使用JWT?

1.首先在專案中新增如下兩個包

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package System.IdentityModel.Tokens.Jwt

也可以直接在Nuget包管理工具中搜尋

2.建立JwtOptions模型類,同時在appsetting.json中新增對應設定

    public class JwtOptions
    {
        /// <summary>
        /// 簽發者
        /// </summary>
        public string Issuer { get; set; }

        /// <summary>
        /// 接收者
        /// </summary>
        public string Audience { get; set; }

        /// <summary>
        /// 金鑰
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// 過期時間
        /// </summary>
        public int ExpireSeconds { get; set; }
    }
  "JWT": {
    "Issuer": "簽發方",
    "Audience": "接受方",
    "Key": "A86DA130-1B95-4748-B3B2-1B6AA9F2F743",//加密金鑰
    "ExpireSeconds": 600 //金鑰過期時間
  }

3.建立JWTExtensions靜態類,新增AddJWTAuthentication擴充套件方法

    public static class JWTExtensions
    {
        public static AuthenticationBuilder AddJWTAuthentication(this IServiceCollection services, JwtOptions jwtOptions)
        {
            return services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(x =>
                {
                    x.TokenValidationParameters = new()
                    {
                        ValidateIssuer = true,//是否驗證發行商
                        ValidateAudience = true,//是否驗證受眾者
                        ValidateLifetime = true,//是否驗證失效時間
                        ValidateIssuerSigningKey = true,//是否驗證簽名鍵
                        ValidIssuer = jwtOptions.Issuer,
                        ValidAudience = jwtOptions.Audience,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Key))
                    };
                });
        }
    }

4.建立SwaggerGenOptionsExtensions靜態類,新增AddAuthenticationHeader擴充套件方法,為swagger增加Authentication報文頭

 

    public static class SwaggerGenOptionsExtensions
    {
        /// <summary>
        /// 為swagger增加Authentication報文頭
        /// </summary>
        /// <param name="option"></param>
        public static void AddAuthenticationHeader(this SwaggerGenOptions option)
        {
            option.AddSecurityDefinition("Authorization",
                new OpenApiSecurityScheme
                {
                    Description = "Authorization header. \r\nExample:Bearer 12345ABCDE",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Authorization"
                }
                ); ;

            option.AddSecurityRequirement(new OpenApiSecurityRequirement()
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference=new OpenApiReference
                        {
                            Type=ReferenceType.SecurityScheme,
                            Id="Authorization"
                        },
                        Scheme="oauth2",
                        Name="Authorization",
                        In=ParameterLocation.Header,
                    },
                    new List<string>()
                }
            });
        }
    }

5.建立IJwtService介面及實現JwtService類,其為構建token服務

    public interface IJwtService
    {
        string BuildToken(IEnumerable<Claim> claims, JwtOptions options);
    }
    public class JwtService : IJwtService
    {
        public string BuildToken(IEnumerable<Claim> claims, JwtOptions options)
        {
            //過期時間
            TimeSpan timeSpan = TimeSpan.FromSeconds(options.ExpireSeconds);//token過期時間
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(options.Key));//加密的token金鑰
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);//簽名證書,其值為securityKey和HmacSha256Signature演演算法
            var tokenDescriptor = new JwtSecurityToken(options.Issuer, options.Audience, claims, expires: DateTime.Now.Add(timeSpan), signingCredentials: credentials);//表示jwt token的描述資訊,其值包括Issuer簽發方,Audience接收方,Claims載荷,過期時間和簽名證書
            return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);//使用該方法轉換為字串形式的jwt token返回
        }
    }

6.將上述服務盡數註冊

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IJwtService, JwtService>();
JwtOptions jwtOpt = builder.Configuration.GetSection("JWT").Get<JwtOptions>();
builder.Services.AddJWTAuthentication(jwtOpt);
builder.Services.Configure<SwaggerGenOptions>(c =>
{
    c.AddAuthenticationHeader();
});

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthentication();//注意,一定得先啟動這個
app.UseAuthorization();
//以下回答來自GPT
//app.UseAuthentication()是啟用身份驗證中介軟體,它會驗證請求中的身份資訊,並將身份資訊儲存在HttpContext.User屬性中。而app.UseAuthorization()是啟用授權中介軟體,它會檢查HttpContext.User中的身份資訊是否有存取當前請求所需的許可權。
//一定要先啟用身份驗證中介軟體再啟用授權中介軟體,因為授權中介軟體需要使用身份驗證中介軟體儲存的身份資訊來進行許可權驗證。如果沒有啟用身份驗證中介軟體,授權中介軟體將無法獲取到身份資訊,從而無法進行許可權驗證。
app.MapControllers();
app.Run();

7.在控制器中新增[ApiController]特性開啟jwt鑑權,在登入介面中返回token

    [ApiController]
    [Route("[controller]/[action]")]
    [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;
        //jwt服務

        private readonly IJwtService _jwtService;

        private readonly IConfiguration _configuration;

        public WeatherForecastController(ILogger<WeatherForecastController> logger, IJwtService jwtService, IConfiguration configuration)
        {
            _logger = logger;
            _jwtService = jwtService;
            _configuration = configuration;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }

        //AllowAnonymous允許匿名存取
        [AllowAnonymous, HttpGet]
        public string GetToken()
        {
            var jwtopntion = _configuration.GetSection("JWT").Get<JwtOptions>();
            List<Claim> claims = new List<Claim>();
            claims.Add(new Claim(ClaimTypes.Name, "使用者1"));
            claims.Add(new Claim(ClaimTypes.Role, "超級管理員"));
            return _jwtService.BuildToken(claims, jwtopntion);
        }
    }

效果測試

 

直接呼叫Get方法返回401,鑑權失敗

 

 呼叫GetToken方法,取得token

 點選右上角綠色按鈕

 value中輸入的值為bearer,空一格,加上之前取得的token,點選授權

 呼叫成功