SwaggerUI为Asp.net Core提供JWT支持?

时间:2016-09-08 23:22:45

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

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

有必要使用Fiddler发送请求。击败Swagger的全部意义。

2 个答案:

答案 0 :(得分:5)

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

看起来像这样的摇摇欲坠:

Visual of authorization input

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

答案 1 :(得分:0)

使用.NET Core 1.0和Swagger UI进行JWT承载令牌身份验证

第1步: 在WebAPI项目的根目录中创建Options文件夹,并使用名称&#34; JwtIssuerOptions.cs&#34;创建一个类。 第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中的Auhtorization参数中传递带有Bearer + WhiteSpace + Token字样的令牌。然后执行,它会给你想要的结果......

享受JWT ......