JWT不管是基于角色,還是自定義政策,實作的步驟都是大同小異的,基于自定義政策的步驟如下:
1、appsettings.json中配置JWT參
2、添加身份認證和授權服務和中間件,并設定為政策模式和政策名稱
3、定義生成Token的方法和驗證Toekn參數的方法
4、登入時驗證身份并分發Toekn
5、繼承AuthorizationHandler<IAuthorizationRequirement>,實作鑒權的規則
接下來看看具體實作。
JWT配置
"JWTConfig": {
"Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
"Issuer": "gsw",
"Audience": "everone",
"Expires": 10000
}
實作自定義政策
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
var builder = WebApplication.CreateBuilder();
//綁定JWT配置文年
var jwtConfig = new JWTConfig();
builder.Configuration.GetSection("JWTConfig").Bind(jwtConfig);
builder.Services.AddSingleton(jwtConfig);
//這裡是注入權限資料,也可以放在緩存中,以便鑒權時用
builder.Services.AddSingleton(new List<Permission> { new Permission { RoleName = "admin", Url = "/helloadmin", Method = "get" } });
//注入自定義政策處理類型
builder.Services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
//注入身分驗證和授權,并且是Policy的名稱為Permission
builder.Services
.AddAuthorization(options =>
{
var permissionRequirement = new PermissionRequirement();
options.AddPolicy("Permission", policy => policy.AddRequirements(permissionRequirement));
})
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opt =>
{
opt.RequireHttpsMetadata = false;
opt.TokenValidationParameters = JwtToken.CreateTokenValidationParameters(jwtConfig);
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
//map三個get請求,都是.RequireAuthorization("Permission"),基于Permission政策來驗證的
app.MapGet("/hellosystem", (ILogger<Program> logger, HttpContext context) =>
{
var message = #34;hello,system,{context.User?.Identity?.Name}";
logger.LogInformation(message);
return message;
}).RequireAuthorization("Permission");
app.MapGet("/helloadmin", (ILogger<Program> logger, HttpContext context) =>
{
var message = #34;hello,admin,{context.User?.Identity?.Name}";
logger.LogInformation(message);
return message;
}).RequireAuthorization("Permission");
app.MapGet("/helloall", (ILogger<Program> logger, HttpContext context) =>
{
var message = #34;hello,all roles,{context.User?.Identity?.Name}";
logger.LogInformation(message);
return message;
}).RequireAuthorization("Permission");
//登入,并分發Token
app.MapPost("/login", [AllowAnonymous] (ILogger<Program> logger, LoginModel login, JWTConfig jwtConfig) =>
{
logger.LogInformation("login");
if (login.UserName == "gsw" && login.Password == "111111")
{
var now = DateTime.UtcNow;
var claims = new Claim[] {
new Claim(ClaimTypes.Role, "admin"),
new Claim(ClaimTypes.Name, "桂素偉"),
new Claim(ClaimTypes.Sid, login.UserName),
new Claim(ClaimTypes.Expiration, now.AddSeconds(jwtConfig.Expires).ToString())
};
var token = JwtToken.BuildJwtToken(claims, jwtConfig);
return token;
}
else
{
return "username or password is error";
}
});
app.Run();
//登入實體
public class LoginModel
{
public string? UserName { get; set; }
public string? Password { get; set; }
}
//JWT配置文年
public class JWTConfig
{
public string? Secret { get; set; }
public string? Issuer { get; set; }
public string? Audience { get; set; }
public int Expires { get; set; }
}
//Token功能類
public class JwtToken
{
public static dynamic BuildJwtToken(Claim[] claims, JWTConfig jwtConfig)
{
var now = DateTime.UtcNow;
var jwt = new JwtSecurityToken(
issuer: jwtConfig.Issuer,
audience: jwtConfig.Audience,
claims: claims,
notBefore: now,
expires: now.AddSeconds(jwtConfig.Expires),
signingCredentials: GetSigningCredentials(jwtConfig)
);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
Status = true,
AccessToken = encodedJwt,
ExpiresIn = now.AddSeconds(jwtConfig.Expires),
TokenType = "Bearer"
};
return response;
}
static SigningCredentials GetSigningCredentials(JWTConfig jwtConfig)
{
var keyByteArray = Encoding.ASCII.GetBytes(jwtConfig?.Secret!);
var signingKey = new SymmetricSecurityKey(keyByteArray);
return new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
}
public static TokenValidationParameters CreateTokenValidationParameters(JWTConfig jwtConfig)
{
var keyByteArray = Encoding.ASCII.GetBytes(jwtConfig?.Secret!);
var signingKey = new SymmetricSecurityKey(keyByteArray);
return new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = jwtConfig?.Issuer,
ValidateAudience = true,
ValidAudience = jwtConfig?.Audience,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
}
}
//權限實本類
public class Permission
{
public string? RoleName { get; set; }
public string? Url { get; set; }
public string? Method { get; set; }
}
//自定義政策授權時的參數類型,這時沒參數,是以是個空類型
public class PermissionRequirement : IAuthorizationRequirement
{
}
//自定義政策授權的處理類型
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly List<Permission> _userPermissions;
public PermissionHandler(List<Permission> permissions)
{
_userPermissions = permissions;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
if (context.Resource is DefaultHttpContext)
{
var httpContext = context.Resource as DefaultHttpContext;
var questPath = httpContext?.Request?.Path;
var method = httpContext?.Request?.Method;
var isAuthenticated = context?.User?.Identity?.IsAuthenticated;
if (isAuthenticated.HasValue && isAuthenticated.Value)
{
var role = context?.User?.Claims?.SingleOrDefault(s => s.Type == ClaimTypes.Role)?.Value;
if (_userPermissions.Where(w => w.RoleName == role && w.Method?.ToUpper() == method?.ToUpper() && w.Url?.ToLower() == questPath).Count() > 0)
{
context?.Succeed(requirement);
}
else
{
context?.Fail();
}
}
}
return Task.CompletedTask;
}
}
運作結果如下:
1、沒有登入,傳回401
2、登入,取token
3、正确通路
4、沒有授權通路,傳回403