Используйте JWT (Authorization: Bearer) в Swagger в ASP.NET Core

Я создаю REST api в ASP.NET Core 1.0. Я использовал Swagger для тестирования, но теперь я добавил авторизацию JWT для некоторых маршрутов. (с UseJwtBearerAuthentication)

Можно ли изменить заголовок запросов Swagger, чтобы маршруты с атрибутом [Authorize] могли быть протестированы?

Ответ 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"
         });
      }
   }
}

Затем вы увидите дополнительный текст авторизации TextBox в своем swagger, где вы можете добавить свой токен в формате "Bearer {jwttoken}", и вы должны быть авторизованы в своих запросах swagger.

Ответ 2

В настоящее время Swagger имеет функциональность для аутентификации с помощью JWT-токена и может автоматически добавлять токен в заголовок (я использую Swashbuckle.AspNetCore 1.1.0).

enter image description here

Следующий код должен помочь достичь этого.

В 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; }
}

Ответ 3

Чтобы расширить ответ HansVG, который работал у меня (спасибо), и поскольку у меня недостаточно очков для вклада, я не могу ответить на вопрос emseetea напрямую. После того, как у вас есть текстовое поле авторизации, вам нужно будет вызвать конечную точку, которая генерирует токен, который будет находиться за пределами вашей конечной точки [Авторизовать].

Как только вы вызвали эту конечную точку для создания маркера с конечной точки, вы можете скопировать ее из результатов для этой конечной точки. Затем у вас есть токен для использования в других областях, которые должны [Авторизовать]. Просто вставьте его в текстовое поле. Удостоверьтесь, что, как отметил HansVG, добавить его в правильном формате, который должен включать "носитель". Формат = "предъявитель {токен}".

Ответ 4

Благодаря ответу Павла К. я наконец решил эту проблему в 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

Вы можете добавить любой дополнительный заголовок с помощью вызова 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[]{}}
    });
});

enter image description here