[NSwag] Swagger UI + JWT Authentication 訪問受保護的 Web API

之前有寫過用 Web API 2 整合 JWT [ASP.NET Web API] 實作 System.IdentityModel.Tokens.Jwt 進行身分驗證,到了 ASP.NET Core 之後,用法沒有太大變化,不過我個人認為驗證的注入設定可讀性變的更高了...

ASP.NET Core 3.1

開發環境

  • VS 2019
  • ASP.NET Core 3.1
  • NSwag.AspNetCore.13.2.5

實作

安裝套件

Install-Package Microsoft.AspNetCore.Authentication
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.Extensions.DependencyInjection
Install-Package Microsoft.Extensions.Options
Install-Package MNSwag.AspNetCore

 

JwtAuthenticationProvider.cs

模擬 User 假物件,驗證帳號

產生 / 驗證 JWT:JwtSecurityTokenHandler 的用法沒有太大的改變

public class JwtAuthenticationProvider : IJwtAuthenticationProvider
{
    private readonly AppSettings _appSettings;
 
    // users hardcoded for simplicity, store in a db with hashed passwords in production applications
    private readonly List<User> _fakeUsers = new List<User>
    {
        new User {Id = 1, FirstName = "Test", LastName = "User", Username = "yao", Password = "123456"}
    };
 
    public JwtAuthenticationProvider(IOptions<AppSettings> appSettings)
    {
        this._appSettings = appSettings.Value;
    }
 
    public string Authenticate(string userName, string password)
    {
        var user = this._fakeUsers.SingleOrDefault(x => x.Username == userName && x.Password == password);
 
        // return null if user not found
        if (user == null)
        {
            return null;
        }
 
        // authentication successful so generate jwt token
        var tokenHandler         = new JwtSecurityTokenHandler();
        var key                  = Encoding.UTF8.GetBytes(this._appSettings.Secret);
        var symmetricSecurityKey = new SymmetricSecurityKey(key);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, userName),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // JWT ID
 
                //new Claim(ClaimTypes.Name, user.Id.ToString())
            }),
            Expires = DateTime.UtcNow.AddDays(7),
            SigningCredentials =
                new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

 

Startup.cs

注意順序!

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
 
    app.UseHttpsRedirection();
 
    app.UseRouting();
 
    // global cors policy
    app.UseCors(x => x
                     .AllowAnyOrigin()
                     .AllowAnyMethod()
                     .AllowAnyHeader());
 
    app.UseAuthentication();
    app.UseAuthorization();
 
    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
 
    // Add OpenAPI/Swagger middlewares
    app.UseOpenApi();    // Serves the registered OpenAPI/Swagger documents by default on `/swagger/{documentName}/swagger.json`
    app.UseSwaggerUi3(); // Serves the Swagger UI 3 web ui to view the OpenAPI/Swagger documents by default on `/swagger`
}

 

注入 JwtAuthenticationProvider

services.AddSingleton<IJwtAuthenticationProvider, JwtAuthenticationProvider>();

Key 存放在組態設定 appsettings.json

key 要 16 字元以上

var key = Encoding.ASCII.GetBytes(appSettings.Secret);

appsettings.json,內容如下

"AppSettings": {
  "Secret": "D8AE7CCF-8E64-4843-9CC6-76EBBB87B440"
},

 

注入組態設定

services.Configure<AppSettings>(appSettingsSection);
 

注入驗證

services.AddAuthentication().AddJwtBearer(x =>{});

 

注入 NSwag 

框起來的地方要一樣

完整代碼如下:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
    services.AddControllers();
    services.AddSingleton<IJwtAuthenticationProvider, JwtAuthenticationProvider>();
 
    // configure strongly typed settings objects
    var appSettingsSection = this.Configuration.GetSection("AppSettings");
    var appSettings        = appSettingsSection.Get<AppSettings>();
 
    services.Configure<AppSettings>(appSettingsSection);
 
    // configure jwt authentication
    var key = Encoding.ASCII.GetBytes(appSettings.Secret);
    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(key),
                                  ValidateIssuer           = false,
                                  ValidateAudience         = false
                              };
                          });
 
    services.AddOpenApiDocument(document =>
                                {
                                    var openApiSecurityScheme = new OpenApiSecurityScheme
                                    {
                                        Type        = OpenApiSecuritySchemeType.ApiKey,
                                        Name        = "Authorization",
                                        In          = OpenApiSecurityApiKeyLocation.Header,
                                        Description = "Type into the textbox: Bearer {your JWT token}."
                                    };
                                    document.AddSecurity("JWT",
                                                         Enumerable.Empty<string>(),
                                                         openApiSecurityScheme);
 
                                    document.OperationProcessors
                                            .Add(new AspNetCoreOperationSecurityScopeProcessor("JWT"));
                                });
}

 

測試

用一個 Action 驗證帳密、產生 JWT 

[Authorize]
[ApiController]
public class TokenController : ControllerBase
{
    private readonly IJwtAuthenticationProvider _jwtProvider;
 
    public TokenController(IJwtAuthenticationProvider jwtProvider)
    {
        this._jwtProvider = jwtProvider;
    }
 
    [AllowAnonymous]
    [HttpPost("login")]
    public async Task<IActionResult> LoginAsync(LoginRequest login)
    {
        var token = this._jwtProvider.Authenticate(login.UserId, login.Password);
        if (string.IsNullOrWhiteSpace(token))
        {
            IActionResult result = new BadRequestObjectResult(new {Message = "Invalid Authorization Header"});
            return result;
        }
 
        return this.Ok(token);
    }
}

 

登入成功後,取得 Token,把他複製下來

 

在 Swagger UI 輸入授權

Bearer {token}

最後訪問受保護的資源,得到 200,資料也印出來了

 

範例位置

https://github.com/yaochangyu/sample.dotblog/tree/master/WebAPI/NSwag/Lab.JwtAuth

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo