【问题标题】:JWT support in SwaggerUI for Asp.net Core?用于 Asp.net Core 的 SwaggerUI 中的 JWT 支持?
【发布时间】:2018-01-19 18:05:23
【问题描述】:

我编写了一个 Asp.Net 核心 REST 服务并获得了一些基本的 JWT 支持。如何让 swagger 测试页面发送 BEARER 令牌?

不得不使用 Fiddler 发送请求有点烦人。打败了 Swagger 的全部意义。

【问题讨论】:

    标签: c# asp.net-core jwt swagger-ui


    【解决方案1】:

    在 ConfigureSwaggerDocument() 扩展方法中,您可以将 SecurityDefinitions 添加到 SwaggerDocumentOptions。示例:

    options.SecurityDefinitions.Add("yourapi_oauth2", new OAuth2Scheme()
                {
                    Description = "OAuth2 client credentials flow",
                    Type = "oauth2",
                    Flow = "clientcredentials",
                    AuthorizationUrl = Configuration["OpenId:authority"],
                    TokenUrl = Configuration["OpenId:authority"] + "/connect/token",
                    Scopes = new Dictionary<string, string>() { { "yourapi", "your api resources"} }
                } );
                options.OperationFilter<ApplyOAuth2Security>();
                options.DocumentFilter<ApplyOAuth2Security>();
    

    ApplyOAuth2Security 是一个实现 IDocumentFilter 和 IOperationFilter 的自定义类,用于告诉配置 Swagger 使用您的授权方式。示例继续:

        public class ApplyOAuth2Security : IDocumentFilter, IOperationFilter
    {
        public void Apply(Operation operation, OperationFilterContext context)
        {
            var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
            var isAuthorized = filterPipeline.Select(f => f.Filter).Any(f => f is AuthorizeFilter);
            var authorizationRequired = context.ApiDescription.GetControllerAttributes().Any(a => a is AuthorizeAttribute);
            if (!authorizationRequired) authorizationRequired = context.ApiDescription.GetActionAttributes().Any(a => a is AuthorizeAttribute);
    
            if (isAuthorized && authorizationRequired)
            {
                operation.Parameters.Add(new NonBodyParameter()
                {
                    Name = "Authorization",
                    In = "header",
                    Description = "JWT security token obtained from Identity Server.",
                    Required = true,
                    Type = "string"
                });
            }
        }
    
        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            IList<IDictionary<string, IEnumerable<string>>> security = swaggerDoc.SecurityDefinitions.Select(securityDefinition => new Dictionary<string, IEnumerable<string>>
            {
                {securityDefinition.Key, new string[] {"yourapi"}}
            }).Cast<IDictionary<string, IEnumerable<string>>>().ToList();
    
    
            swaggerDoc.Security = security;
        }
    }
    

    在 swagger-ui 中看起来像这样:

    请记住,您需要完全根据自己的需要调整 IDocumentFiler 和 IOperationFilter 的实现。

    【讨论】:

    • 谢谢 Danny,弹出文本框,我检查了 fiddler 及其发送:授权:abc(我将 abc 放在文本框中)。有没有办法让它预先添加所需的“BEARER”?
    • NonBodyParameter 具有 Default 属性,您可以使用它设置您喜欢的任何默认值(例如:Default = "BEARER")
    • 是的,但是用户可以删除它而不是附加到它,所以我想在幕后自动将“BEARER”添加到文本框文本这可能吗?我见过一些解决方案,他们隐藏真实的文本框并创建一个新的文本框并在幕后同步隐藏的真实文本框,但由于这些解决方案是针对旧的招摇的,所以还没有完全发挥作用。
    • 我自己还没做过。我认为最好在单独的问题中提出这个问题。
    【解决方案2】:

    使用 .NET Core 1.0 和 Swagger UI 进行 JWT 不记名令牌身份验证

    第 1 步: 在 WebAPI 项目的根目录中创建 Options 文件夹,并在其中创建一个名为“JwtIssuerOptions.cs”的类。 第 2 步: 将以下代码粘贴到其中...

    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Threading.Tasks;
    public class JwtIssuerOptions
    {
        /// <summary>
        /// "iss" (Issuer) Claim
        /// </summary>
        /// <remarks>The "iss" (issuer) claim identifies the principal that issued the
        ///   JWT.  The processing of this claim is generally application specific.
        ///   The "iss" value is a case-sensitive string containing a StringOrURI
        ///   value.  Use of this claim is OPTIONAL.</remarks>
        public string Issuer { get; set; }
    
        /// <summary>
        /// "sub" (Subject) Claim
        /// </summary>
        /// <remarks> The "sub" (subject) claim identifies the principal that is the
        ///   subject of the JWT.  The claims in a JWT are normally statements
        ///   about the subject.  The subject value MUST either be scoped to be
        ///   locally unique in the context of the issuer or be globally unique.
        ///   The processing of this claim is generally application specific.  The
        ///   "sub" value is a case-sensitive string containing a StringOrURI
        ///   value.  Use of this claim is OPTIONAL.</remarks>
        public string Subject { get; set; }
    
        /// <summary>
        /// "aud" (Audience) Claim
        /// </summary>
        /// <remarks>The "aud" (audience) claim identifies the recipients that the JWT is
        ///   intended for.  Each principal intended to process the JWT MUST
        ///   identify itself with a value in the audience claim.  If the principal
        ///   processing the claim does not identify itself with a value in the
        ///   "aud" claim when this claim is present, then the JWT MUST be
        ///   rejected.  In the general case, the "aud" value is an array of case-
        ///   sensitive strings, each containing a StringOrURI value.  In the
        ///   special case when the JWT has one audience, the "aud" value MAY be a
        ///   single case-sensitive string containing a StringOrURI value.  The
        ///   interpretation of audience values is generally application specific.
        ///   Use of this claim is OPTIONAL.</remarks>
        public string Audience { get; set; }
    
        /// <summary>
        /// "nbf" (Not Before) Claim (default is UTC NOW)
        /// </summary>
        /// <remarks>The "nbf" (not before) claim identifies the time before which the JWT
        ///   MUST NOT be accepted for processing.  The processing of the "nbf"
        ///   claim requires that the current date/time MUST be after or equal to
        ///   the not-before date/time listed in the "nbf" claim.  Implementers MAY
        ///   provide for some small leeway, usually no more than a few minutes, to
        ///   account for clock skew.  Its value MUST be a number containing a
        ///   NumericDate value.  Use of this claim is OPTIONAL.</remarks>
        public DateTime NotBefore => DateTime.UtcNow;
    
        /// <summary>
        /// "iat" (Issued At) Claim (default is UTC NOW)
        /// </summary>
        /// <remarks>The "iat" (issued at) claim identifies the time at which the JWT was
        ///   issued.  This claim can be used to determine the age of the JWT.  Its
        ///   value MUST be a number containing a NumericDate value.  Use of this
        ///   claim is OPTIONAL.</remarks>
        public DateTime IssuedAt => DateTime.UtcNow;
    
        /// <summary>
        /// Set the timespan the token will be valid for (default is 5 min/300 seconds)
        /// </summary>
        public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(5);
    
        /// <summary>
        /// "exp" (Expiration Time) Claim (returns IssuedAt + ValidFor)
        /// </summary>
        /// <remarks>The "exp" (expiration time) claim identifies the expiration time on
        ///   or after which the JWT MUST NOT be accepted for processing.  The
        ///   processing of the "exp" claim requires that the current date/time
        ///   MUST be before the expiration date/time listed in the "exp" claim.
        ///   Implementers MAY provide for some small leeway, usually no more than
        ///   a few minutes, to account for clock skew.  Its value MUST be a number
        ///   containing a NumericDate value.  Use of this claim is OPTIONAL.</remarks>
        public DateTime Expiration => IssuedAt.Add(ValidFor);
    
        /// <summary>
        /// "jti" (JWT ID) Claim (default ID is a GUID)
        /// </summary>
        /// <remarks>The "jti" (JWT ID) claim provides a unique identifier for the JWT.
        ///   The identifier value MUST be assigned in a manner that ensures that
        ///   there is a negligible probability that the same value will be
        ///   accidentally assigned to a different data object; if the application
        ///   uses multiple issuers, collisions MUST be prevented among values
        ///   produced by different issuers as well.  The "jti" claim can be used
        ///   to prevent the JWT from being replayed.  The "jti" value is a case-
        ///   sensitive string.  Use of this claim is OPTIONAL.</remarks>
        public Func<Task<string>> JtiGenerator =>
          () => Task.FromResult(Guid.NewGuid().ToString());
    
        /// <summary>
        /// The signing key to use when generating tokens.
        /// </summary>
        public SigningCredentials SigningCredentials { get; set; }
    }
    

    第 3 步: 将以下代码粘贴到 Startup.cs 文件中...

    using Swashbuckle.AspNetCore.Swagger;
    using System;
    using Microsoft.Extensions.PlatformAbstractions;
    using Microsoft.IdentityModel.Tokens;
    using System.Text;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc.Authorization;
    using WebAPI.Options;
    
    namespace WebAPI
    {
        public class Startup
        {
             public static string ConnectionString { get; private set; }
             private const string SecretKey = "getthiskeyfromenvironment";
             private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
             public Startup(IHostingEnvironment env)
             {
                var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build(); 
    
            ConnectionString = Configuration.GetSection("ConnectionStrings").GetSection("<Your DB Connection Name>").Value;
        }
    
        public static IConfigurationRoot Configuration { get; private set; }
    
        // This method gets called by the runtime. Use this method to add services to the container
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
    
            // ********************
            // Setup CORS
            // ********************
            var corsBuilder = new CorsPolicyBuilder();
            corsBuilder.AllowAnyHeader();
            corsBuilder.AllowAnyMethod();
            corsBuilder.AllowAnyOrigin(); // For anyone access.
            //corsBuilder.WithOrigins("http://localhost:12345"); // for a specific url. Don't add a forward slash on the end!
            corsBuilder.AllowCredentials();
    
            services.AddCors(options =>
            {
                options.AddPolicy("<YourCorsPolicyName>", corsBuilder.Build());
            });
    
            var xmlPath = GetXmlCommentsPath();
    
            // Register the Swagger generator, defining one or more Swagger documents
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "XYZ API", Version = "v1", Description = "This is a API for XYZ client applications.", });
                c.IncludeXmlComments(xmlPath);
                c.AddSecurityDefinition("Bearer", new ApiKeyScheme() { In = "header", Description = "Please paste JWT Token with Bearer + White Space + Token into field", Name = "Authorization", Type = "apiKey" });
            });
    
            // Add framework services.
            services.AddOptions();
    
            // Use policy auth.
            services.AddAuthorization(options =>
            {
                options.AddPolicy("AuthorizationPolicy",
                                  policy => policy.RequireClaim("DeveloperBoss", "IAmBoss"));
            });
    
            // Get options from app settings
            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
    
            // Configure JwtIssuerOptions
            services.Configure<JwtIssuerOptions>(options =>
            {
                options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
                options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
                options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
            });
        }
    
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
    
                ValidateAudience = true,
                ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
    
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = _signingKey,
    
                RequireExpirationTime = true,
                ValidateLifetime = true,
    
                ClockSkew = TimeSpan.Zero
            };
    
            app.UseJwtBearerAuthentication(new JwtBearerOptions
            {
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                TokenValidationParameters = tokenValidationParameters
            });
    
            //loggerFactory.AddLambdaLogger(Configuration.GetLambdaLoggerOptions());
    
            app.UseMvc();
    
            app.UseStaticFiles();
    
            // Shows UseCors with CorsPolicyBuilder.
            app.UseCors("<YourCorsPolicyName>");
    
            // Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseSwagger();
    
           // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
            app.UseSwaggerUi(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "XYZ API V1");
            });
        }
    
        private string GetXmlCommentsPath()
        {
            var app = PlatformServices.Default.Application;
            return System.IO.Path.Combine(app.ApplicationBasePath, "WebAPI.xml");
        }
    }
    

    }

    第 4 步: 在 Controllers 文件夹中创建 JWTController.cs。并将代码更改如下:

    using Microsoft.AspNetCore.Cors;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using Newtonsoft.Json;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using System.Security.Claims;
    using System.IdentityModel.Tokens.Jwt;
    using WebAPI.Options;
    using System.Security.Principal;
    
    namespace WebAPI.Controllers
    {
        [EnableCors("<YourCorsPolicyName>")]
        [Route("[api/controller]")]
        public class JWTController : Controller
        {
             private readonly JwtIssuerOptions _jwtOptions;
             private readonly ILogger _logger;
             private readonly JsonSerializerSettings _serializerSettings;
    
             public JWTController(IOptions<JwtIssuerOptions> jwtOptions, ILoggerFactory loggerFactory)
        {
            _jwtOptions = jwtOptions.Value;
            ThrowIfInvalidOptions(_jwtOptions);
            _logger = loggerFactory.CreateLogger<JWTController>();
    
            _serializerSettings = new JsonSerializerSettings
            {
                Formatting = Formatting.Indented
            };
            _connectionString = Startup.ConnectionString;
        }
    
        [AllowAnonymous]
        [HttpPost]
        [Route("{username}/{password}")]
        public async Task<IActionResult> Get(string username, string password)
        {
            User user = GetUser(username, password);
            var identity = await GetClaimsIdentity(user);
            if (identity == null)
            {
                _logger.LogInformation($"Invalid username ({username}) or password ({password})");
                return BadRequest("Invalid credentials");
            }
    
            var claims = new[]
              {
                new Claim("UserID",user.UserId.ToString()),
                new Claim("UserName",user.UserName),
                new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
                new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
                identity.FindFirst("DeveloperBoss")
              };
    
            // Create the JWT security token and encode it.
            var jwt = new JwtSecurityToken(
                issuer: _jwtOptions.Issuer,
                audience: _jwtOptions.Audience,
                claims: claims,
                notBefore: _jwtOptions.NotBefore,
                expires: _jwtOptions.Expiration,
                signingCredentials: _jwtOptions.SigningCredentials);
    
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
    
            // Serialize and return the response
            var response = new
            {
                access_token = encodedJwt,
                expires_in = (int)_jwtOptions.ValidFor.TotalSeconds
            };
    
            var json = JsonConvert.SerializeObject(response, _serializerSettings);
            return new OkObjectResult(json);
        }
    
        /// <returns>Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC).</returns>
    
        private static void ThrowIfInvalidOptions(JwtIssuerOptions options)
        {
            if (options == null) throw new ArgumentNullException(nameof(options));
    
            if (options.ValidFor <= TimeSpan.Zero)
            {
                throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor));
            }
    
            if (options.SigningCredentials == null)
            {
                throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials));
            }
    
            if (options.JtiGenerator == null)
            {
                throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator));
            }
        }
    
        /// <returns>Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC).</returns>
        private static long ToUnixEpochDate(DateTime date)
          => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
    
        /// <summary>
        /// IMAGINE BIG RED WARNING SIGNS HERE!
        /// You'd want to retrieve claims through your claims provider
        /// in whatever way suits you, the below is purely for demo purposes!
        /// </summary>
        private static Task<ClaimsIdentity> GetClaimsIdentity(User user)
        {
            if (user == null)
            {
                // Credentials are invalid, or account doesn't exist
                return Task.FromResult<ClaimsIdentity>(null);
            }
            if (user.UserId == 0)
            {
                return Task.FromResult(new ClaimsIdentity(new GenericIdentity(user.UserName, "Token"),
                    new Claim[] { }));
            }
            return Task.FromResult(new ClaimsIdentity(new GenericIdentity(user.UserName, "Token"),
              new[]
              {
                new Claim("DeveloperBoss", "IAmBoss")
              }));
        }
    }
    

    }

    第 5 步: 将您要授权的所有控制器装饰如下:

    namespace WebAPI.Controllers
    {
        /// <summary>
        /// summary comment here
        /// </summary>
        /// <remarks>
        /// remark comment here
        /// </remarks>
        [EnableCors("<YourCorsPloicyName>")]
        [Authorize(Policy = "AuthorizationPolicy")]
        [Route("api/[controller]")]
        public class AbcController : Controller
        {
             //Your class code goes here...
        }
    }
    

    第 6 步: 结论:现在当你直接运行任何 api 时,它会给你 401-Unauthorize。因此,您必须先调用 JWTController api 并从中生成令牌,然后在 Header 中的授权参数中使用 Bearer + WhiteSpace + Token 一词传递该令牌。然后执行,它会给你想要的结果...

    享受 JWT...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-27
      • 2017-07-09
      • 2017-03-31
      • 1970-01-01
      • 2019-09-12
      • 1970-01-01
      • 1970-01-01
      • 2020-05-15
      相关资源
      最近更新 更多