C# Asp.net核心的SwiggerUI中的JWT支持?
我编写了一个Asp.Net核心REST服务,并获得了一些基本的JWT支持。我如何让招摇过市测试页面发送承载令牌C# Asp.net核心的SwiggerUI中的JWT支持?,c#,asp.net-core,jwt,swagger-ui,C#,Asp.net Core,Jwt,Swagger Ui,我编写了一个Asp.Net核心REST服务,并获得了一些基本的JWT支持。我如何让招摇过市测试页面发送承载令牌 使用Fiddler发送请求有点烦人。击败了拥有Swagger的全部意义。在ConfigureSwaggerDocument()扩展方法中,您可以向SwaggerDocumentOptions添加SecurityDefinitions。例如: options.SecurityDefinitions.Add("yourapi_oauth2", new OAuth2Scheme()
使用Fiddler发送请求有点烦人。击败了拥有Swagger的全部意义。在ConfigureSwaggerDocument()扩展方法中,您可以向SwaggerDocumentOptions添加SecurityDefinitions。例如:
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>();
options.SecurityDefinitions.Add(“yourapi_oauth2”,新的OAuth2Scheme()
{
Description=“OAuth2客户端凭据流”,
Type=“oauth2”,
Flow=“clientcredentials”,
AuthorizationUrl=配置[“OpenId:authority”],
TokenUrl=Configuration[“OpenId:authority”]+“/connect/token”,
Scopes=newdictionary(){{“yourapi”,“yourapi资源”}
} );
选项。操作筛选器
请记住,您需要根据自己的需要完全调整IDocumentFiler和IOperationFilter的实现。在ConfigureSwaggerDocument()扩展方法中,您可以向SwaggerDocumentOptions添加SecurityDefinitions。例如:
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>();
options.SecurityDefinitions.Add(“yourapi_oauth2”,新的OAuth2Scheme()
{
Description=“OAuth2客户端凭据流”,
Type=“oauth2”,
Flow=“clientcredentials”,
AuthorizationUrl=配置[“OpenId:authority”],
TokenUrl=Configuration[“OpenId:authority”]+“/connect/token”,
Scopes=newdictionary(){{“yourapi”,“yourapi资源”}
} );
选项。操作筛选器
请记住,您需要完全根据自己的需要调整IDocumentFiler和IOperationFilter的实现。使用.NET Core 1.0和Swagger UI的JWT承载令牌身份验证
第1步:
在WebAPI项目的根目录中创建选项文件夹,并在其中创建一个名为“jwtissueoptions.cs”的类。
第二步:
将以下代码粘贴到其中
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; }
}
使用Microsoft.IdentityModel.Tokens;
使用制度;
使用System.Threading.Tasks;
公共类JwtIssuerOptions
{
///
///“iss”(发行人)索赔
///
///“iss”(发行人)索赔确定了发行债券的委托人
///本权利要求的处理通常是特定于应用的。
///“iss”值是包含StringOrURI的区分大小写的字符串
///值。此声明的使用是可选的。
公共字符串颁发者{get;set;}
///
///“次级”(主题)索赔
///
///“sub”(subject)索赔确定了作为
///JWT的主题。JWT中的权利要求通常是声明
///关于主题。主题值的作用域必须为
///在发行人的上下文中是本地唯一的,或者是全球唯一的。
///此索赔的处理通常是特定于应用程序的
///“sub”值是包含StringOrURI的区分大小写的字符串
///值。此声明的使用是可选的。
公共字符串主题{get;set;}
///
///“澳元”(观众)索赔
///
///“aud”(受众)声明标识了JWT所属的接收者
///拟用于。拟处理JWT的每个委托人必须
///用受众声明中的值标识自己。如果主体
///处理声明时,不会使用
///“aud”索赔如果存在该索赔,则JWT必须
///拒绝。在一般情况下,“aud”值是大小写数组-
///敏感字符串,每个字符串都包含StringOrURI值
///特殊情况下,当JWT有一个受众时,“aud”值可能是
///包含StringOrURI值的单个区分大小写的字符串
///受众价值观的解释通常是特定于应用的。
///此声明的使用是可选的。
公共字符串访问群体{get;set;}
///
///“nbf”(非之前)索赔(默认值为UTC NOW)
///
///“nbf”(非之前)声明确定了JWT
///不得接受处理。“nbf”的处理
///索赔要求当前日期/时间必须晚于或等于
///不在“nbf”声明中列出的日期/时间之前。实现者可以
///提供一些小的回旋余地,通常不超过几分钟,以便
///时钟偏移的帐户。其值必须是包含
///NumericDate值。此声明的使用是可选的。
public DateTime NotBefore=>DateTime.UtcNow;
///
///“iat”(在发出时)索赔(默认值为UTC NOW)
///
///“iat”(于发布)索赔确定了JWT的发布时间
///此声明可用于确定JWT的年龄。其
///值必须是包含NumericDate值的数字。使用此
///索赔是可选的。
public DateTime IssuedAt=>DateTime.UtcNow;
///
///设置令牌有效的时间跨度(默认值为5分钟/300秒)
///
公共TimeSpan ValidFor{get;set;}=TimeSpan.FromMinutes(5);
///
///“exp”(到期时间)索赔(返回IssuedAt+ValidFor)
///
///“exp”(过期时间)声明标识上的过期时间
///或之后,不得接受JWT进行处理
///处理“exp”索赔要求当前日期/时间
///必须在“exp”索赔中列出的到期日期/时间之前。
///实施者可能会提供一些小的回旋余地,通常不超过
///几分钟,以说明时钟偏移。其值必须是一个数字
///包含NumericDate值。此声明的使用是可选的。
公共日期时间到期=>IssuedAt.Add(ValidFor);
///
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");
}
}
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")
}));
}
}
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...
}
}