.net的各种框架啥的都不是很熟悉,当时只是想怎么实现快速校验授权确保api是通过验证之后才能打开。
我说的快速就是不需要写重复的样板代码,通过总体控制,最后发现,似乎也只能通过注解实现,不过也算是比较合理的,不然无法区分哪些需要哪些不需要了
那么实现原理就设计到cookie,session, token,前面的session是需要服务器存档用户信息的,服务器压力比较大,token的实现原理,在登录之后返回 一个token, 之后再每一个接口提交都传递token,
都传递token可以通过请求头提交也可以通过get,post这些提交,我这里探索两者均实现。
问了一些网友,说要实现便捷授权可以实现拦截器,拦截器类似注解,
那么只是单纯的拦截器还不是根本,而且我还是没看懂所以然咋自己通过写拦截器代码实现,
最后不得不直接用框架jwt bearer实现
jwt bearer 接入是只需要添加注解[Authorize]
就可以实现对指定方法的鉴权。
using coreapiauth;
using coreapiauth.TestWebApi.AuthCenter.Utility;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//注入JWT服务
builder.Services.AddScoped<IJwtService, JwtService>();
#region 注册JWT鉴权
var issuer = "study"; //Configuration["issuer"];
var audience = "lozn"; //Configuration["audience"];
var securityKey = "4A9A70D2-B8AD-42E1-B002-553BDEF4E76F";// Configuration["SecurityKey"];
//配置认证服务
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //默认授权机制名称
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters();
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
RequireExpirationTime = true, //过期时间
ValidateIssuerSigningKey = true, //是否验证IssuerSigningKey
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
//自定义校验规则:新登录后,之前的token无效
//AudienceValidator = (audiences, securityToken, validationParameters) =>
//{
// return audiences != null && audiences.FirstOrDefault().Equals(audience);
//}
};
});
#endregion
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseHttpsRedirection();
//1.先开启认证
app.UseAuthentication();
//2.再开启授权
app.UseAuthorization();
app.MapControllers();
app.Run();
网上的文章似乎有些将的不是 core api,有些是core api,但是和我这边开发工具生成的模板代码又不一样,所以我这边是这样子的。
其中
app.UseAuthentication();
app.UseAuthorization();
似乎影响了最后能否正常通过鉴权,我之前是成功获取了token,但是一直卡无法鉴权成功, 请求头应该传递什么,最后得出的结论:
Authorization
为请求头的key ,value为Bearer 登录返回的key
也可以通过get提交,上面的完整代码之所以可以get生效是因为有如下代码生效
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
}
};
那么访问
http://lozn.vaiwan.com/api/Auth/GetAuthData?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJOaWNrTmFtZSI6Ik5ldENvcmUiLCJSb2xlIjoiQWRtaW5pc3RyYXRvciIsImV4cCI6MTY1NDA3MDY2NywiaXNzIjoic3R1ZHkiLCJhdWQiOiJsb3puIn0.7GQOQybhFLFXpL0FO31WNdm-o8MYJHS5F-y1xB-PQPU
我写的这个源码有两个地方都用到了秘钥,
其中在启动的代码中配置的是如下
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
RequireExpirationTime = true, //过期时间
ValidateIssuerSigningKey = true, //是否验证IssuerSigningKey
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
//自定义校验规则:新登录后,之前的token无效
//AudienceValidator = (audiences, securityToken, validationParameters) =>
//{
// return audiences != null && audiences.FirstOrDefault().Equals(audience);
//}
};
而在登录的时候返回 token用到的代码
public string GetToken(string name)
{
/**
* Claims(Payload)
* Claims包含了一些跟这个token有关的重要信息。JWT标准规定的字段:
*
* iss: The issuer of the token, 签发人
* sub: The subject of the token, 主题
* exp: Expiration Time. 过期时间(Unix时间戳格式)
* iat: Issued At. 签发时间(Unix时间戳格式)
* jti: JWT ID. 编号
* aud: audience. 受众
* nbf: Not Before. 生效时间
*
* 除了规定的字段外,可以包含其他任何JSON兼容的字段。
* */
var claims = new[]
{
new Claim(ClaimTypes.Name, name),
new Claim("NickName", "NetCore"),
new Claim("Role", "Administrator")
};
if (_configuration["SecurityKey"] == null)
{
throw new Exception("please config SecurityKey issuer audience at appsettings.json");
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["issuer"], //签发人
audience: _configuration["audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(20), //20分钟有效期
signingCredentials: credentials);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
return tokenStr;
}
如果两者的issuer,audience,或者key不一致,一样是无法鉴权成功的,为了确保 SecurityKey在appsetting.json里面定义我直接加了检测,不存在直接抛出异常。
就可以成功实现访问,而非返回401,如果鉴权不成功是返回401的
最后能成功实现,还是参考了如下比较有参考意义的文章,前者并没有提到怎么去传递请求头,以及configuration参数怎么传递也没提及,所以搞的我一直没成功,
swagger测试是否觉得不方便了呢,可以给swagger弄一把锁锁的代码实现原理就是你点击这个锁输入key,它会给特定的请求头自动加key从而实现传参测试,
//注册 Swagger 服务
builder.Services.AddSwaggerGen(options =>
{
//下面代码实现加锁
options.SwaggerDoc("v1", new OpenApiInfo { Title = "test API", Version = "v1" });
//获取xml文件名
//var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
//获取xml文件路径
//var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
// 添加控制器层注释,true表示显示控制器注释
//options.IncludeXmlComments(xmlPath, true);
#region swagger 用 Jwt验证
//开启权限小锁
options.OperationFilter<AddResponseHeadersFilter>();
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
//在header中添加token,传递到后台
options.OperationFilter<SecurityRequirementsOperationFilter>();
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传递)直接在下面框中输入Bearer {token}(注意两者之间是一个空格) \"",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey
});
#endregion
});
image.png
image.png image.png
自定义异常返回json
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; //这里使用默认的Scheme来进行验证,想
options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler); //403
});
再加上
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //默认授权机制名称
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters();
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
RequireExpirationTime = true, //过期时间
ValidateIssuerSigningKey = true, //是否验证IssuerSigningKey
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
//自定义校验规则:新登录后,之前的token无效
//AudienceValidator = (audiences, securityToken, validationParameters) =>
//{
// return audiences != null && audiences.FirstOrDefault().Equals(audience);
//}
};
}).AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
也就是说和前面的代码对比要加上
AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
没加上会提示下面的错误
xxx
no-authenticationscheme-was-specified-and-there-was-no-defaultchallengescheme-
关于
System.InvalidOperationException:“EndpointRoutingMiddleware matches endpoints setup by EndpointMiddleware and so must be added to the request execution pipeline before EndpointMiddleware. Please add EndpointRoutingMiddleware by calling 'IApplicationBuilder.UseRouting' inside the call to 'Configure(...)' in the application startup code.”
这个错误就是addRoute要加上而且加在之前。
app.UseRouting();
//1.先开启认证
app.UseAuthentication();
//2.再开启授权
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
})
完整源码暂时不放出来了,因为第一篇文章已经算文章了,而我提供的program.cs也解决了如何读取configration的问题,以及写了一篇关于配置文件configration是啥玩意的问题。
网友评论