目的
在swagger內使用jwt token測試API
建立新專案
選擇ASP.NET Core Web API專案範本,並執行下一步
設定新的專案
命名你的專案名稱,並選擇專案要存放的位置。
其他資訊
選擇.net6版本,支援OpenAPI支援一定要勾選,此選項.net5以後才會有,.net core 3.1並沒有此選項,需要從NuGet安裝,並點建立
專案基本設定
右邊紅框處專案檔點兩下,會開啟專案的xml檔案,額外加入兩行xml資料,目的是要透過編譯器產生文件檔案
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
- 加入前
- 加入後
編輯Program.cs檔案
修改program檔案內容,調整AddSwaggerGen的內容,目的是為了可以讀取我們所寫的註解
builder.Services.AddSwaggerGen(options => {
// using System.Reflection;
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
- 加入前
- 加入後
NuGet加入套件
透過NuGet安裝
- JWT
- Microsoft.AspNetCore.Authentication.JwtBearer
- Microsoft.IdentityModel.Tokens
- System.IdentityModel.Tokens.Jwt
新增Helpers資料夾並在裡面新增JwtHelpers.cs類別檔案
jwt範例使用保哥範例來做修改,目的只是為了取得jwt token
public class JwtHelper {
private readonly JwtSettingsOptions _settings;
public JwtHelper(IOptionsMonitor<JwtSettingsOptions> settings) {
//注入appsetting的json
_settings = settings.CurrentValue;
}
public string GenerateToken(string userName, int expireMinutes = 120) {
//發行人
var issuer = _settings.Issuer;
//加密的key,拿來比對jwt-token沒有
var signKey = _settings.SignKey;
建立JWT-Token
var token = JwtBuilder.Create()
//所採用的雜湊演算法
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
//加密key
.WithSecret(signKey)
//角色
.AddClaim("roles", "admin")
//JWT ID
.AddClaim("jti", Guid.NewGuid().ToString())
//發行人
.AddClaim("iss", issuer)
//使用對象名稱
.AddClaim("sub", userName) // User.Identity.Name
//過期時間
.AddClaim("exp", DateTimeOffset.UtcNow.AddMinutes(expireMinutes).ToUnixTimeSeconds())
//此時間以前是不可以使用
.AddClaim("nbf", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
//發行時間
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
//使用者全名
.AddClaim(ClaimTypes.Name, userName)
//進行編碼
.Encode();
return token;
}
}
//將appsetting轉為強行別所使用
public class JwtSettingsOptions {
public string Issuer { get; set; } = "";
public string SignKey { get; set; } = "";
}
因篇幅過長,只擷取JwtHelpers.cs部分內容,記得要using下列命名空間
using JWT.Algorithms;
using JWT.Builder;
using Microsoft.Extensions.Options;
using System.Security.Claims;
新增Filters資料夾並在裡面新增AuthorizeCheckOperationFilter.cs類別檔案
因為在使用swagger做認證測試時,會遇到一個很惱人的問題,就是當我的某些api並不需要做認證,卻還是會在畫面上顯示鎖頭
- 第一支並沒有attribute,無須認證
- 第二支為AllowAnonymous,無須認證
- 第三支為Authorize,需要認證
public class AuthorizeCheckOperationFilter : IOperationFilter {
private readonly EndpointDataSource _endpointDataSource;
public AuthorizeCheckOperationFilter(EndpointDataSource endpointDataSource) {
_endpointDataSource = endpointDataSource;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context) {
取得所有controller內的action
var Descriptor = _endpointDataSource.Endpoints.FirstOrDefault(x =>
x.Metadata.GetMetadata<ControllerActionDescriptor>() == context.ApiDescription.ActionDescriptor);
//取得包含Authorize的Attribute
var Authorize = Descriptor.Metadata.GetMetadata<AuthorizeAttribute>() != null;
//取得包含AllowAnonymous的Attribute
var AllowAnonymous = Descriptor.Metadata.GetMetadata<AllowAnonymousAttribute>() != null;
//如果不需要鎖頭則return回去
if (!Authorize || AllowAnonymous)
return;
//需要鎖頭則在swagger-UI中定義出來
operation.Security = new List<OpenApiSecurityRequirement>
{
new()
{
[
new OpenApiSecurityScheme {Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"}
}
] = new List<string>()
}
};
}
}
編輯WeatherForecastController檔案
注入JwtHelper
- 第一支為預設沒有attribute的方法
- 第二支為登入方法,attribute是AllowAnonymous,任何人都可以使用
- 第三支為登入後才可以取得的資料,attribute是Authorize(Roles = “admin”),role需要是admin才可以使用
[HttpGet("Login"), AllowAnonymous]
public ActionResult<string> Login(string username , string password) {
var token = _jwtHelpers.GenerateToken(username);
return Ok(token);
}
[HttpGet("username"), Authorize(Roles = "admin")]
public ActionResult<string> Username() {
return Ok(User.Identity?.Name);
}
再次編輯Program.cs檔案
分兩個區塊說明
- JWT設定
//清除預設映射
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
//註冊JwtHelper
builder.Services.AddSingleton<JwtHelper>();
//使用選項模式註冊
builder.Services.Configure<JwtSettingsOptions>(
builder.Configuration.GetSection("JwtSettings"));
//設定認證方式
builder.Services
//使用bearer token方式認證並且token用jwt格式
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
// 可以讓[Authorize]判斷角色
RoleClaimType = "roles",
// 預設會認證發行人
ValidateIssuer = true,
ValidIssuer = builder.Configuration.GetValue<string>("JwtSettings:Issuer"),
// 不認證使用者
ValidateAudience = false,
// 如果 Token 中包含 key 才需要驗證,一般都只有簽章而已
ValidateIssuerSigningKey = true,
// 簽章所使用的key
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue<string>("JwtSettings:SignKey")))
};
});
2. Swagger-UI調整
//說明api如何受到保護
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
Name = "Authorization",
//選擇類型,type選擇http時,透過swagger畫面做認證時可以省略Bearer前綴詞(如下圖)
Type = SecuritySchemeType.Http,
//採用Bearer token
Scheme = "Bearer",
//bearer格式使用jwt
BearerFormat = "JWT",
//認證放在http request的header上
In = ParameterLocation.Header,
//描述
Description = "JWT驗證描述"
});
//製作額外的過濾器,過濾Authorize、AllowAnonymous,甚至是沒有打attribute
options.OperationFilter<AuthorizeCheckOperationFilter>();
- Type使用
SecuritySchemeType.Http
,不用打Bearer - Type使用
SecuritySchemeType.ApiKey
,需要打Bearer與空白以及文字描述會包含Name、In、Description 在下方別忘了使用認證的中介層
執行結果
在登入的api輸入帳號密碼 會回傳一組jwt token 點擊認證按鈕,將token輸入 使用有鎖頭的API 最後可以正確取得回傳值就是成功了 如果不輸入token直接使用有鎖頭的API,就會跳出401錯誤