【问题标题】:Use JWT (Authorization: Bearer) in Swagger in ASP.NET Core在 ASP.NET Core 的 Swagger 中使用 JWT(授权:Bearer)
【发布时间】:2016-12-11 14:12:27
【问题描述】:

我正在 ASP.NET Core 1.0 中创建一个 REST api。我使用 Swagger 进行测试,但现在我为某些路由添加了 JWT 授权。 (UseJwtBearerAuthentication)

是否可以修改 Swagger 请求的标头,以便可以测试具有 [Authorize] 属性的路由?

【问题讨论】:

  • @MichaelFreidgeim 这个问题是在链接问题之前提出的,所以看起来链接问题是重复的
  • “可能重复”是一种清理方法 - 关闭类似问题并保留最佳答案。日期不是必需的。见meta.stackexchange.com/questions/147643/…如果你同意它需要澄清,请投票meta.stackexchange.com/questions/281980/…
  • @MichaelFreidgeim 我同意需要清理。此页面上的解决方案对我有用,这就是我将答案标记为已接受的原因。在另一页上,OP 没有费心检查这个问题是否已经被问过,也没有将答案标记为已接受,所以我不知道这应该如何成为主要问题,而这个页面是重复的。另一页上的答案之一甚至提到了这里的答案。此页面包含所有可能的答案,另一个没有,因此请将另一个标记为重复。

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


【解决方案1】:

我遇到了同样的问题,并在这篇博文中找到了一个可行的解决方案: http://blog.sluijsveld.com/28/01/2016/CustomSwaggerUIField

归结为在您的配置选项中添加它

services.ConfigureSwaggerGen(options =>
{
   options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
});

以及操作过滤器的代码

public class AuthorizationHeaderParameterOperationFilter : IOperationFilter
{
   public void Apply(Operation operation, OperationFilterContext context)
   {
      var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
      var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
      var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);

      if (isAuthorized && !allowAnonymous)
      {
          if (operation.Parameters == null)
             operation.Parameters = new List<IParameter>();

          operation.Parameters.Add(new NonBodyParameter
          {                    
             Name = "Authorization",
             In = "header",
             Description = "access token",
             Required = true,
             Type = "string"
         });
      }
   }
}

然后你会在你的 swagger 中看到一个额外的授权文本框,你可以在其中添加格式为“Bearer {jwttoken}”的令牌,并且你应该在你的 swagger 请求中获得授权。

【讨论】:

  • 在 swagger ui 中使用试用功能时,您从哪里获得要放入 jwttoken 字段的不记名令牌?
  • 只是一个快速的帮手;使用 Microsoft.AspNetCore.Mvc.Authorization;使用 Swashbuckle.AspNetCore.Swagger;使用 Swashbuckle.AspNetCore.SwaggerGen;使用 System.Collections.Generic;使用 System.Linq;
  • 授权字段如何自动分配Bearer
  • 是的,这可行,但很麻烦。像@hubert17 我想自动将令牌添加到请求中。这适用于 4.5.2 api,但不适用于核心 2.0。
  • 如果您遇到困难,NonBodyParameterIParameter 可以替换为 OpenApiParameter
【解决方案2】:

为了扩展对我有用的 HansVG 答案(谢谢),由于我没有足够的贡献点,我无法直接回答 emseetea 问题。获得授权文本框后,您将需要调用生成令牌的端点,该令牌将在端点的必须 [Authorize] 区域之外。

调用该端点以从该端点生成令牌后,您可以将其从该端点的结果中复制出来。然后,您就可以在必须 [Authorize] 的其他区域中使用该令牌。只需将其粘贴到文本框中。正如 HansVG 所提到的,确保以正确的格式添加它,其中需要包含“bearer”。格式 = "承载 {token}"。

【讨论】:

    【解决方案3】:

    目前 Swagger 具有使用 JWT-token 进行身份验证的功能,并且可以自动将令牌添加到标头中(我使用的是 Swashbuckle.AspNetCore 1.1.0)。

    下面的代码应该有助于实现这一点。

    在 Startup.ConfigureServices() 中:

    services.AddSwaggerGen(c =>
    {
        // Your custom configuration
        c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
        c.DescribeAllEnumsAsStrings();
        // JWT-token authentication by password
        c.AddSecurityDefinition("oauth2", new OAuth2Scheme
        {
            Type = "oauth2",
            Flow = "password",
            TokenUrl = Path.Combine(HostingEnvironment.WebRootPath, "/token"),
            // Optional scopes
            //Scopes = new Dictionary<string, string>
            //{
            //    { "api-name", "my api" },
            //}
        });
    });
    

    如果您的端点不同,请检查并配置 TokenUrl

    在 Startup.Configure() 中:

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
    
        // Provide client ID, client secret, realm and application name (if need)
    
        // Swashbuckle.AspNetCore 4.0.1
        c.OAuthClientId("swagger-ui");
        c.OAuthClientSecret("swagger-ui-secret");
        c.OAuthRealm("swagger-ui-realm");
        c.OAuthAppName("Swagger UI");
    
        // Swashbuckle.AspNetCore 1.1.0
        // c.ConfigureOAuth2("swagger-ui", "swagger-ui-secret", "swagger-ui-realm", "Swagger UI");
    });
    

    如果您的通过令牌进行身份验证的端点遵循 OAuth2 标准,那么一切都应该有效。但以防万一,我添加了此端点的示例:

    public class AccountController : Controller
    {
        [ProducesResponseType(typeof(AccessTokens), (int)HttpStatusCode.OK)]
        [ProducesResponseType((int)HttpStatusCode.BadRequest)]
        [ProducesResponseType((int)HttpStatusCode.Unauthorized)]
        [HttpPost("/token")]
        public async Task<IActionResult> Token([FromForm] LoginModel loginModel)
        {
            switch (loginModel.grant_type)
            {
                case "password":
                    var accessTokens = // Authentication logic
                    if (accessTokens == null)
                        return BadRequest("Invalid user name or password.");
                    return new ObjectResult(accessTokens);
    
                case "refresh_token":
                    var accessTokens = // Refresh token logic
                    if (accessTokens == null)
                        return Unauthorized();
                    return new ObjectResult(accessTokens);
    
                default:
                    return BadRequest("Unsupported grant type");
            }
        }
    }
    
    public class LoginModel
    {
        [Required]
        public string grant_type { get; set; }
    
        public string username { get; set; }
        public string password { get; set; }
        public string refresh_token { get; set; }
        // Optional
        //public string scope { get; set; }
    }
    
    public class AccessTokens
    {
        public string access_token { get; set; }
        public string refresh_token { get; set; }
        public string token_type { get; set; }
        public int expires_in { get; set; }
    }
    

    【讨论】:

    • 这行得通,除了当 UserId/Password/Client/Secret 失败时,它只是在后台悄悄失败并仍然显示已登录。有什么想法吗?
    • 如果授权失败,请检查您是否返回 HTTP 状态码 400。这是 RFC 6749 的要求,Swagger 也处理它。我已经更新了答案。
    • 是的,我使用 IdentityServer 4,它返回 400。但是 swagger UI 显示 Logout 按钮,好像用户已成功登录。我不确定如何配置该 swagger 弹出屏幕以显示身份验证失败。
    • 我在我的 ASP.NET Core 应用程序中使用 Swashbuckle.AspNetCore 4.0.1 包。它无法识别ConfigureOAuth2() 方法。我错过了什么吗?编译错误:“SwaggerUIOptions”不包含“ConfigureOAuth2”的定义,并且找不到接受“SwaggerUIOptions”类型的第一个参数的可访问扩展方法“ConfigureOAuth2”(您是否缺少 using 指令或程序集引用?)跨度>
    • @Tohid 请检查更新的答案,在 Swashbuckle.AspNetCore 4.0.1 中,API 发生了一些变化。
    【解决方案4】:

    感谢Pavel K.'s answer,这是我最终在 ASP.NET Core 2.2 中使用 Swagger 4.0.1 解决此问题的方法。

    在 Startup.cs 的 ConfigureServices() 中:

    public void ConfigureServices(IServiceCollection services)
    {
        .
        .
        .
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info { Title = "...", Version = "v1" });
            .
            .
            .
            c.AddSecurityDefinition("Bearer", new OAuth2Scheme
            {
                Flow = "password",
                TokenUrl = "/token"
            });
    
           // It must be here so the Swagger UI works correctly (Swashbuckle.AspNetCore.SwaggerUI, Version=4.0.1.0)
           c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
           {
               {"Bearer", new string[] { }}
           });
        });
        .
        .
        .
    }
    

    在 Startup.cs Configure() 中:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        .
        .
        .
        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "...");
            // Provide client ID, client secret, realm and application name (if need)
            c.OAuthClientId("...");
            c.OAuthClientSecret("...");
            c.OAuthRealm("...");
            c.OAuthAppName("...");
        });
        .
        .
        .
    }
    

    下面是我如何创建一个端点来发出 JWT 令牌:

    [ApiController, Route("[controller]")]
    public class TokenController : ControllerBase
    {
        [HttpPost, AllowAnonymous]
        public async Task<ActionResult<AccessTokensResponse>> RequestToken([FromForm]LoginRequest request)
        {
            var claims = await ValidateCredentialAndGenerateClaims(request);
    
            var now = DateTime.UtcNow;
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_setting.SecurityKey));
            var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken(
                issuer: _setting.Issuer,
                audience: _setting.Audience,
                claims: claims,
                notBefore: now,
                expires: now.AddMinutes(_setting.ValidDurationInMinute),
                signingCredentials: signingCredentials);
    
            return Ok(new AccessTokensResponse(token));
        }
    }
    

    您验证用户名和密码(和/或 client_id 和 clinet_secret)的所有规则和逻辑都将在 ValidateCredentialAndGenerateClaims() 中。

    如果您只是想知道,这些是我的请求和响应模型:

    /// <summary>
    /// Encapsulates fields for login request.
    /// </summary>
    /// <remarks>
    /// See: https://www.oauth.com/oauth2-servers/access-tokens/
    /// </remarks>
    public class LoginRequest
    {
        [Required]
        public string grant_type { get; set; }
        public string username { get; set; }
        public string password { get; set; }
        public string refresh_token { get; set; }
        public string scope { get; set; }
    
        public string client_id { get; set; }
        public string client_secret { get; set; }
    }
    
    /// <summary>
    /// JWT successful response.
    /// </summary>
    /// <remarks>
    /// See: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
    /// </remarks>
    public class AccessTokensResponse
    {
        /// <summary>
        /// Initializes a new instance of <seealso cref="AccessTokensResponse"/>.
        /// </summary>
        /// <param name="securityToken"></param>
        public AccessTokensResponse(JwtSecurityToken securityToken)
        {
            access_token = new JwtSecurityTokenHandler().WriteToken(securityToken);
            token_type = "Bearer";
            expires_in = Math.Truncate((securityToken.ValidTo - DateTime.UtcNow).TotalSeconds);
        }
    
        public string access_token { get; set; }
        public string refresh_token { get; set; }
        public string token_type { get; set; }
        public double expires_in { get; set; }
    }
    

    【讨论】:

      【解决方案5】:

      您可以使用此swagger configuration 在 API 调用中添加任何额外的标头

      // Register the Swagger generator, defining 1 or more Swagger documents
      services.AddSwaggerGen(c =>
      {
          c.SwaggerDoc("v1", new Info
          {
              Version = "v1",
              Title = "Core API",
              Description = "ASP.NET Core API",
              TermsOfService = "None",
              Contact = new Contact
              {
                  Name = "Raj Kumar",
                  Email = ""
              },
              License = new License
              {
                  Name = "Demo"
              }
          });
          c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
          {
              Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
              Name = "Authorization",
              In = "header",
              Type = "apiKey"
          });
          c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
          {
          {"Bearer",new string[]{}}
          });
      });
      

      【讨论】:

        【解决方案6】:

        我还会检查 AuthorizeAttribute

        var filterDescriptor = context.ApiDescription.ActionDescriptor.FilterDescriptors;
        
        var hasAuthorizedFilter = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
        var allowAnonymous = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
        var hasAuthorizedAttribute = context.MethodInfo.ReflectedType?.CustomAttributes.First().AttributeType ==
                                             typeof(AuthorizeAttribute);
        
        if ((hasAuthorizedFilter || hasAuthorizedAttribute) && !allowAnonymous)
        {
            var oAuthScheme = new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
            };
        
            operation.Security = new List<OpenApiSecurityRequirement>
            {
                new OpenApiSecurityRequirement
                {
                    [ oAuthScheme ] = new List<string>()
                }
            };
        }
        

        控制器动作:

        [Authorize(Policy = AppConfiguration.PermissionReadWrite)]
        [Route("api/[controller]")]
        [ApiController]
        public class FooController : ControllerBase
        {
           ...
        }
        

        【讨论】:

          【解决方案7】:

          【讨论】:

            猜你喜欢
            • 2017-09-12
            • 1970-01-01
            • 2021-10-05
            • 2017-07-10
            • 2018-08-19
            • 2017-07-02
            • 2019-04-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多