1,认证(Authenication)
1.1 基本步骤
- 安装相关
nuget
包:Microsoft.AspNetCore.Authentication.JwtBearer
- 准备配置信息
- 注册服务
- 调用中间件
- 实现一个
JwtHelper
用于生成Token
- 控制器限制访问(添加
Authorize
标签)
1.2 Nuget 包安装
在控制台中:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer -v 8.0.7
在包程序中:
1.3 配置信息
在 appsettings.json
中新增节点信息:
"JwtConfig": {
"SecretKey": "jokdsp;fgjsiolepjhgfisroedghrdsoliughdrtiughrdikughdriughiulghrdeilug", // 密钥
"Audience": "test",
"Issuer": "test", // 颁发者
"Expired": 1 // 过期时间(分钟)
}
新增一个JwtConfig
对象类,用于读取配置文件信息
public class JwtConfig {
/// <summary>
/// 密钥Key
/// </summary>
public string? SecretKey { get; set; }
/// <summary>
/// 接收者
/// </summary>
public string? Issuer { get; set; }
/// <summary>
/// 生效时间(分钟)
/// </summary>
public int Expired { get; set; }
/// <summary>
/// 颁发者
/// </summary>
public string? Audience { get; set; }
/// <summary>
/// 生效时间
/// </summary>
public DateTime NoteBeofre => DateTime.Now;
/// <summary>
/// 过期时间
/// </summary>
public DateTime Expiration => DateTime.Now.AddMinutes(Expired);
/// <summary>
/// 密钥
/// </summary>
public SecurityKey SignKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
/// <summary>
/// 加密后的密钥,使用SHA256
/// </summary>
public SigningCredentials SigningCredentials => new SigningCredentials(SignKey, SecurityAlgorithms.HmacSha256);
}
1.4 实现 JwtHelper 工具类
主要用于生成和解析 Token
using AuthWebApplication.Config;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
namespace AuthWebApplication.Helper {
public class JwtHelper {
private JwtConfig _jwtConfig;
public JwtHelper(JwtConfig config) {
_jwtConfig = config;
}
public string GetAccessToken () {
var claims = new List<Claim>() {
new Claim("UserID", "123456"),
new Claim("UserName", "Admin")
};
var jwtSecurityToken = new JwtSecurityToken(
issuer: _jwtConfig.Issuer,
audience: _jwtConfig.Audience,
claims,
notBefore: _jwtConfig.NoteBeofre,
expires: _jwtConfig.Expiration,
signingCredentials: _jwtConfig.SigningCredentials
);
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return token;
}
public List<Claim> GetClaims (HttpRequest request) {
var authorization = request.Headers["Authorization"].ToString();
var auth = authorization.Split(" ")[1];
var handler = new JwtSecurityTokenHandler();
//反解密,获取其中的Claims
var payload = handler.ReadJwtToken(auth).Payload;
var claims = payload.Claims;
return claims.ToList();
}
}
}
1.5 注册服务和调用中间件
在这里针对services
新增一个扩展方法JwtService
,用于注册Jwt
服务
public static class JwtService {
public static void AddJwtService(this IServiceCollection services, ConfigurationManager config) {
var jwtConfig = new JwtConfig();
// 新增 JwtConfig 节点信息与 JwtConfig对象类做绑定,方便读取数据。
config.Bind("JwtConfig", jwtConfig);
services.AddSingleton(jwtConfig);
// 将 JwtHelper 工具类注册为单例模式
services.AddSingleton<JwtHelper>();
services.AddAuthentication(option => {
// 认证配置
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters {
ValidIssuer = jwtConfig.Issuer, // 发行人
ValidAudience = jwtConfig.Audience, // 订阅人
IssuerSigningKey = jwtConfig.SignKey, // SecurityKey
ValidateLifetime = true, // 验证Token有效期
ValidateIssuer = true, // 是否验证Issure
ValidateAudience = true, // 是否验证 Audience
ValidateIssuerSigningKey = true, // 是否验证SecurityKey
RequireExpirationTime = true, // 是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30) // 缓冲时间,默认5分钟
};
});
}
}
调用中间件,在Program.cs
增加一下代码
// 要在授权之前认证,必须在身份认证中间件之前调用,比如(UserAuthorization)
app.UseAuthentication();
app.UseAuthorization(); // 授权
1.6 控制器配置
using AuthWebApplication.Helper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AuthWebApplication.Controllers {
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase{
private readonly JwtHelper jwtHelper;
public AccountController(JwtHelper jwtHelper) {
this.jwtHelper = jwtHelper;
}
[HttpGet("login")]
public Result Get() {
var token = jwtHelper.GetAccessToken();
return Result.Success(token);
}
[HttpGet("test")]
[Authorize]
public Result test () {
return Result.Success("success");
}
}
}
1.7 测试调用
在这里使用ApiFox
进行测试,在调用 /api/Account/login 生成 Token
在调用 /api/Account/GetTest 时传入 test,得到返回结果
2,授权(Authorization)
授权是指用户可以执行那些操作,比如用户是否可以对文档的创建、编辑添加等。
授权(Authorization)
和认证(Authenication)
是相互独立的
2.1 基本属性
Authorize
允许经过身份验证的用户访问该组件,AllowAnoymous
允许未身份验证的用户访问单个操作。如果 2 个属性结合使用,系统会忽略Authorize
属性。
授权规则可以是 Roles(角色),Policy(策略),AuthenticationSchemes(方案)
[Authorize(Roles = "", Policy = "", AuthenticationSchemes = "")]
2.2 授权方式
基本上授权只有:Policy、Role、Scheme 这3种方式,对应 Authorize 标签的3个属性。
2.2.1 Roles 角色授权
基于角色授权,用户拥有这个角色,就能通过授权验证。
在认证时给用户添加相应的Claim
,即可表示该用户拥有该角色,一个用户可以拥有多个角色。
new Claim(ClaimTypes.Role, "admin"),
new Claim(ClaimTypes.Role, "user")
在 Controller
或Action
中
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "user")]
public class TestController : ControllerBase{
private JwtHelper _jwtHelper;
public TestController(JwtHelper jwtHelper) { _jwtHelper = jwtHelper; }
[Authorize(Roles = "admin")] // 需要用户 user和admin两个角色才可以访问
[HttpGet("get2")]
public string Get2 () => "Get2";
[Authorize(Roles = "admin,user")] // 拥有 admin或user 其中一个角色就能访问
[HttpGet("get1")]
public Result Get1 () => Result.Success("get1");
}
2.2.2 Policy 策略授权
一个Policy
可以包含一个或多个要求,可以是Roles
匹配,也可以是Claims
匹配等。
下面是一个基础案例,添加两个policy
- policy1,要求
Cliams
必须包含一个Employee
- policy2,要求 Claim 必须包含一个 Employee 且值必须要 1,2 或 3 其中的一个。
services.AddAuthorization(options => {
options.AddPolicy("policy1", x => x.RequireClaim("Employee"));
options.AddPolicy("policy2", x => x.RequireClaim("Employee", "1", "2", "3"));
});
在控制器或Action
上添加标签Authorize
即可生效
[Authorize(Policy = "policy1")]
public class PolicyController : ControllerBase
2.3 基于策略 Policy 的自定义授权
在配置中调用AddPolicy
来注册策略,在某些情况下,可能无法采用此方式注册所有策略,在这些情况下,可以使用自定义IAuthorizationPolicyProvider
来控制如何提供授权策略,比如在运行时使用外部数据源中的信息策略确定授权要求。
授权的主要方式,是通过 Handler
程序判断授权,需要实现IAuthorizationHandler
接口。
步骤:
- 自定义一个
Requirement
实现IAuthorizationRequirement
- 自定义 Handler 程序继承
AuthorizationHandler<TRequirement>
并重写HandleRequirementAsync
方法。 - 实现策略提供的
PolicyProvider
来动态添加策略。
2.3.1 实现 AuthorizeAttribute
新增属性,继承AuthorizeAttribute
为我们添加额外属性来增加自定义授权,属性Policy
不可为 null。
public class PermissionAttribute : AuthorizeAttribute{
const string _permission = "_auth_permission:";
public string? per {
get {
return per;
}
set {
// Policy 为必须值,在为per更改时,与Policy同步。
// 如果为null,自定义的策略则不会响应。
Policy = _permission + value?.ToString();
}
}
public PermissionAttribute(string per) {
this.per = per;
}
public PermissionAttribute () {
}
}
2.3.2 实现 Requirement
public class PermissionRequirement : IAuthorizationRequirement{
public string? per { get; set; }
public PermissionRequirement(string per) { this.per = per;}
}
属性 per 表示权限名
2.3.3 实现策略提供程序 PolicyProvider
通过配置的方式添加策略不灵活,无法动态添加。通过实现IAuthorizationPolicyProvider
并添加到DI
中,可实现动态Policy
。
IAuthorizationPolicyProvider
默认实现为DefaultAuthorizationPolicyProvider
实现一个PolicyProvider
代码如下:
public class PermissionPolicyProvider : DefaultAuthorizationPolicyProvider,IAuthorizationPolicyProvider {
// 只能使用一个授权策略,如果自定义实现不处理所有策略,他应该退回到默认实现(在这里继承DefaultAuthorizationPolicyProvider,使用原有的实现。)
public PermissionPolicyProvider (IOptions<AuthorizationOptions> options) : base(options) {
}
public async new Task<AuthorizationPolicy> GetDefaultPolicyAsync () {
return await base.GetDefaultPolicyAsync ();
}
public async new Task<AuthorizationPolicy?> GetFallbackPolicyAsync () {
return await base.GetFallbackPolicyAsync();
}
// 通过字符串名称进行查找
public new Task<AuthorizationPolicy?> GetPolicyAsync (string policyName) {
if(policyName.StartsWith("_auth_permission")){
var policy = new AuthorizationPolicyBuilder();
var targetName = policyName.Substring("_auth_permission:".Length);
policy.AddRequirements(new PermissionRequirement(targetName));
var build = policy.Build();
if(build == null) {
}
return Task.FromResult(build);
}
// 如果名称与期望的格式不匹配,使用默认实现。
return base.GetPolicyAsync(policyName);
}
}
**注意:**只会生效最后一个添加的PolicyProvider
,自定义 Provider 必须实现IAuthorizationPolicyProvider
,否则添加到 DI 不生效。
2.3.4 实现 Handler
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement> {
private ILogger<PermissionAuthorizationHandler> _logger;
public PermissionAuthorizationHandler(ILogger<PermissionAuthorizationHandler> logger) {
_logger = logger;
}
Dictionary<string, List<string>> keys = new Dictionary<string, List<string>>() {
// 为1123 添加test2的查询和添加权限
{"1123", new List<string> {"test.get", "test.add"} }
};
// 检查上下文是否符合给全要求
protected override Task HandleRequirementAsync (AuthorizationHandlerContext context, PermissionRequirement requirement) {
var claim = context.User.Claims.FirstOrDefault(x => x.Type == "UserID");
if(claim == null) {
_logger.LogWarning("未找到Permission 相关权限。");
return Task.CompletedTask;
}
// 权限判断,可以是外部数据,数据库等权限配置。
if(!keys.ContainsKey(claim.Value)) {
context.Fail();
return Task.CompletedTask;
}
var rule = keys[claim.Value].FirstOrDefault(x => x == requirement.per);
if(rule == null) {
context.Fail();
return Task.CompletedTask;
}
// 成功
context.Succeed(requirement);
// 失败
//context.Fail();
return Task.CompletedTask;
}
}
运行HandleRequirementAsync
时, 会将用户的 Claim 中 ClaimType 为 userId 的项取出,检测是否有执行权限。
在 Programe.cs 中,将自定义的 PolicyProvider 和 Handler 添加到DI
中:
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
2.3.5 在 Action 或 Controller 中
[ApiController]
[Route("api/[controller]")]
public class Test2Controller : ControllerBase{
[HttpGet("get")]
[Permission(per = "test.get")]
public Result get() => Result.Success("success");
[HttpGet("add")]
[Permission(per = "test.add")]
public Result add () => Result.Success("add");
[HttpGet("edit")]
[Permission(per = "test.edit")]
public Result edit () => Result.Success("edit");
}
2.3.6 测试
在上述案例中,定义只可执行带有test.get``test.add
标签的 Action。
未定义edit
,在访问时会出现 403 未授权。
2.4 自定义AuthorizationMiddleware
自定义IAuthorizationMiddlewareResultHandler
处于授权结果,用于:
- 返回自定义的响应
- 增强默认质询或禁止响应。
public class SampleAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();
public async Task HandleAsync(
RequestDelegate next,
HttpContext context,
AuthorizationPolicy policy,
PolicyAuthorizationResult authorizeResult)
{
// If the authorization was forbidden and the resource had a specific requirement,
// provide a custom 404 response.
if (authorizeResult.Forbidden
&& authorizeResult.AuthorizationFailure!.FailedRequirements
.OfType<Show404Requirement>().Any())
{
// Return a 404 to make it appear as if the resource doesn't exist.
context.Response.StatusCode = StatusCodes.Status404NotFound;
return;
}
// Fall back to the default implementation.
await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
}
在 Program.cs 中注册 IAuthorizationMiddlewareResultHandler 的实现:
builder.Services.AddSingleton<
IAuthorizationMiddlewareResultHandler, SampleAuthorizationMiddlewareResultHandler>();