目录
2.2.1 引用“Microsoft.AspNetCore.Authentication.JwtBearer”
2.2.2 调用 AddAuthentication 和 AddJwtBearer 注册服务
2.2.3 在 appsettings.json 配置密钥、颁发者等参数
一、目的
当前开发一个Api服务时,可能存在多种服务调用请求场景,例如:App\Web\小程序、内部项目、第三方对接等请求Api。本文展示基于Abp.vnext应用架构下,使用JWT支持多种场景的认证和授权。
认证流程:

多种认证和授权流程:

二、解决方案
2.1 什么是认证和授权
认证:即身份认证,因为Htpp协议是无状态的,每个Http请求发过来是无法识别身份的。为了进行身份识别,一般需要你提供一个账号和密码进行登录,登录通过则表示你是正常用户,给你颁发一个身份标识。通过这个身份标识去访问其他服务时,会检查你是否有有这身份标识,有则标识为认证用户,运行请求,没有则会拒绝。
举个例子:看演唱会或则坐车,买了票后,需要凭相关票据才可进入会场。
授权:即授予权限,当你认证通过后需要请求某个页面或则某个服务方法时,会检查分配的唯一标识是否有权限。
举个例子:看演唱会买了看台的票,你想去坐VIP位置,这显然是需要加钱的。
2.2 支持标准 JWT Bearer 认证
使用流程:
- 显式安装 NuGet 包
- 调用 AddAuthentication 和 AddJwtBearer 注册服务
- 在 appsettings.json 配置密钥、颁发者等参数
2.2.1 引用“Microsoft.AspNetCore.Authentication.JwtBearer”
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.IdentityModel.Tokens
dotnet add package System.IdentityModel.Tokens.Jwt
2.2.2 调用 AddAuthentication 和 AddJwtBearer 注册服务
基础示例:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => { /* 参数配置 */ });
实际使用:
namespace Demo.HttpApi.Host;
public class DemoHttpApiHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
var configuration = services.GetConfiguration();
// 跨域
context.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// Configure Jwt Authentication
ConfigureAuthentication(context);
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// 定义在2者之间
//app.UseRouting();
app.UseAuthentication();
//app.UseConfiguredEndpoints();
}
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
var services = context.Services;
var configuration = services.GetConfiguration();
string issuer = configuration["Jwt:Issuer"];
string audience = configuration["Jwt:Audience"];
string expire = configuration["Jwt:ExpireMinutes"];
TimeSpan expiration = TimeSpan.FromMinutes(Convert.ToDouble(expire));
SecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecurityKey"]));
services.AddAuthentication(s =>
{
// 2、Authentication
s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(s =>
{
// 3、Use Jwt bearer
s.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = issuer,
ValidAudience = audience,
IssuerSigningKey = key,
ClockSkew = expiration,
ValidateLifetime = true
};
s.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
}
}
2.2.3 在 appsettings.json 配置密钥、颁发者等参数
{
"Jwt": {
"Issuer": "***.com",//发行人
"Audience": "***.com",//接收者
"SecurityKey": "*****",//密钥
"ExpireMinutes": "60"//过期时间,单位:分钟
}
}
2.3 自定义认证
在某些情况下我们需要添加一些内部的自定义认证,用于识别内部系统调用,或则是其他第三方接口,用于执行一些特殊处理逻辑。
增加自定义的Scheme:“InternalAuth”.通过AddScheme添加认证逻辑,以及认证处理器来处理认证逻辑,完成对于调用端身份认证。
/// <summary>
/// jwt设置
/// </summary>
/// <param name="context"></param>
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
var services = context.Services;
var configuration = services.GetConfiguration();
string issuer = configuration["Jwt:Issuer"];
string audience = configuration["Jwt:Audience"];
string expire = configuration["Jwt:ExpireMinutes"];
TimeSpan expiration = TimeSpan.FromMinutes(Convert.ToDouble(expire));
SecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecurityKey"]));
services.AddAuthentication(s =>
{
// 2、Authentication
s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(s =>
{
// 3、Use Jwt bearer
s.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = issuer,
ValidAudience = audience,
IssuerSigningKey = key,
ClockSkew = expiration,
ValidateLifetime = true
};
s.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
})// 添加内部认证方案
.AddScheme<InternalAuthOptions, InternalAuthHandler>(
"InternalAuth",
opts => { }); // 可以配置选项如果有需要;
}
public class InternalAuthOptions : AuthenticationSchemeOptions
{
// 可以添加自定义选项,比如头名称等
}
public class InternalAuthHandler : AuthenticationHandler<InternalAuthOptions>
{
public InternalAuthHandler(
IOptionsMonitor<InternalAuthOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authHeader = Request.Headers["X-Internal-Auth-Token"].ToString();
// 这里添加你的内部认证逻辑
if (string.IsNullOrEmpty(authHeader) || authHeader != "你的内部认证密钥")
{
return Task.FromResult(AuthenticateResult.Fail("内部认证令牌无效"));
}
// 创建身份主体
var claims = new[] { new Claim(ClaimTypes.Name, "InternalSystem") };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
2.4 自定义授权处理
2.4.1需要实现自定义授权处理器
public class InternalRequirement : IAuthorizationRequirement
{
}
public class InternalHandler : AuthorizationHandler<InternalRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
InternalRequirement requirement)
{
// 如果通过JWT或者内部认证任一成功,则授权通过
if (context.User.Identity.IsAuthenticated)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
2.4.2 配置授权策略
/// <summary>
/// jwt授权设置
/// </summary>
/// <param name="context"></param>
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
var services = context.Services;
var configuration = services.GetConfiguration();
string issuer = configuration["Jwt:Issuer"];
string audience = configuration["Jwt:Audience"];
string expire = configuration["Jwt:ExpireMinutes"];
TimeSpan expiration = TimeSpan.FromMinutes(Convert.ToDouble(expire));
SecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecurityKey"]));
services.AddAuthorization(options =>
{
// 1、Definition authorization policy
options.AddPolicy("Permission", policy => policy.Requirements.Add(new InternalRequirement()));
}).AddAuthentication(s =>
{
// 2、Authentication
s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(s =>
{
// 3、Use Jwt bearer
s.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = issuer,
ValidAudience = audience,
IssuerSigningKey = key,
ClockSkew = expiration,
ValidateLifetime = true
};
s.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
})// 添加内部认证方案
.AddScheme<InternalAuthOptions, InternalAuthHandler>(
"InternalAuth",
opts => { }); // 可以配置选项如果有需要;
//注入授权处理器
services.AddSingleton<IAuthorizationHandler, InternalHandler>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// 定义在2者之间
//app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
//app.UseConfiguredEndpoints();
}
2.5 配置授权策略
在控制器或应用服务上使用授权特性:
[Authorize(Policy = "InternalAuth")]
public class MyAppService : ApplicationService
{
// ...
}
针对特定方法:
[Authorize(Policy = "InternalAuth")]
public async Task<MyDto> GetDataAsync()
{
// ...
}
若有多个Scheme和Policy :
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme},InternalAuth", Policy = "Permission")]
public class MyAppService : ApplicationService
{
// ...
}
三、文章总结
在.netCore中认证和授权都是通过中间件的方式进行处理,弄清楚实现原理,在今后的开发中会更易于针对不同场景进行扩展和应用。文章内部当前未加入对于Jwt Token生成部分,这块网络挺多的就不重复编写了。
把之所学以文载之,欢迎大家多多交流~