我编写了一个Asp.Net核心REST服务,并获得了一些基本的JWT支持。如何让swagger测试页面发送BEARER令牌?
有必要使用Fiddler发送请求。击败Swagger的全部意义。
答案 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;
}
}
看起来像这样的摇摇欲坠:
请记住,您需要完全根据自己的需要调整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 ......