1.引入相关程序包JwtBearer注意版本:
2.配置文件appsettings.json写相关配置参数(也可不写,写在程序里面,数据库读取也是一样的)
, //JWT加密
"JWTToken": {
"SecretKey": "jsaduwqe6asdjewejdue7dfmsdfu0sdfmwmsd8wfsd6", //密钥
"Issuer": "ZYP", //发行者
"Audience": "simple", //拥护者
//"ExpireMinutes": 240 //过期时间
}
3.在Program配置相关服务。
#region JWT
//获取配置文件
var configuration = builder.Configuration;
string Issuer = configuration["JWTToken:Issuer"];
string Audience = configuration["JWTToken:Audience"];
string SecretKey = configuration["JWTToken:SecretKey"];
//注入jwt
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
//过期时间容错值,解决服务器端时间不同步问题(秒)
//允许服务器时间偏移量30秒,即我们配置的过期时间加上这个允许偏移的时间值,才是真正过期的时间(过期时间 + 偏移值)你也可以设置为0,ClockSkew = TimeSpan.Zero
ClockSkew = TimeSpan.FromSeconds(30),
//要求Token的Claims中必须包含Expires
RequireExpirationTime = true,
//是否在令牌期间验证签发者
ValidateIssuer = true,
//发行人Issuer
ValidIssuer = Issuer,
//是否验证接收者
ValidateAudience = true,
//是否验证失效时间
ValidateLifetime = true,
//是否验证签名SecurityKey
ValidateIssuerSigningKey = true,
//接收者
ValidAudience = Audience,
//密钥SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)),
};
});
//注入JwtHelper
builder.Services.AddSingleton(new JwtHelper(configuration));
#endregion
//注入Swagger,注入这个才能在调试接口时输入token
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference=new OpenApiReference{Id="Bearer",Type=ReferenceType.SecurityScheme},
},
Array.Empty<string>()
}
});
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "请输入文字'Bearer '后面跟空格和token格式 Bearer {token}",
Name = "Authorization",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey
});
});
//配置跨域
builder.Services.AddCors(policy =>
{
policy.AddPolicy("CorsPolicy", opt => opt
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("X-Pagination"));
});
//调用中间件:UseAuthentication(认证),
//必须在所有需要身份认证的中间件前调用,比如 UseAuthorization(授权)。
app.UseAuthentication();
//调用中间件:UseAuthorization(授权)。
app.UseAuthorization();
4.相关配置结束后,我们得生成Token,这时我们创建一个专门生成Token的类里面有两个生成Token的方法,想用哪个用哪个。该类在Program里有引用。
/// <summary>
/// Token生成类
/// </summary>
public class JwtHelper
{
/// <summary>
/// 配置文件信息
/// </summary>
private readonly IConfiguration _configuration;
public JwtHelper(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// 创建一个使用控制器方法授权的Token
/// </summary>
/// <returns></returns>
public string CreatePermissionToken(string UserName, string RoleName, string AppId, Claim[] claims)
{
// 1. 定义需要使用到的Claims
if (claims == null)
{
claims = new[]
{
new Claim(ClaimTypes.Name, UserName), //HttpContext.User.Identity.Name
new Claim(ClaimTypes.Role, RoleName), //HttpContext.User.IsInRole("r_admin")
new Claim(JwtRegisteredClaimNames.Jti, AppId),//分配给订阅着的特定Id
new Claim("Permission", Permissions.UserCreate),
new Claim("Permission", Permissions.UserDelete),
new Claim("Permission", Permissions.UserUpdate),
new Claim("Permission", Permissions.UserSelect)
//new Claim("Username", "Admin"),//其他荷载信息
};
}
// 2. 从 appsettings.json 中读取密钥SecretKey
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWTToken:SecretKey"]));
// 3. 选择加密算法
var algorithm = SecurityAlgorithms.HmacSha256;
// 4. 生成Credentials
var signingCredentials = new SigningCredentials(secretKey, algorithm);
// 5. 根据以上,生成token
var Token = new JwtSecurityToken(
issuer: _configuration["JWTToken:Issuer"], //发布者
audience: _configuration["JWTToken:Audience"], //接收者
claims: claims, //存放的用户信息
notBefore: DateTime.Now, //发布时间
expires: System.DateTime.Now.AddHours(48), //有效期设置为48小时
signingCredentials: signingCredentials //数字签名,用于生成Token的Header,其余都是荷载数据
);
// 6. 将token变为string
var token = new JwtSecurityTokenHandler().WriteToken(Token);
return token;
}
/// <summary>
/// 创建一个使用账号密码授权验证的Token
/// </summary>
/// <param name="UserName"></param>
/// <param name="RoleName"></param>
/// <param name="AppId"></param>
/// <param name="Account"></param>
/// <param name="PassWord"></param>
/// <returns></returns>
public string CreateLoginToken(string UserName, string RoleName, string AppId, string Account, string PassWord)
{
// 1. 定义需要使用到的Claims
var claims = new[]
{
new Claim(ClaimTypes.Name, UserName),
new Claim(ClaimTypes.Role, RoleName),
new Claim(JwtRegisteredClaimNames.Jti, AppId),//分配给订阅着的特定Id
new Claim("Account", Account),//账号
new Claim("PassWord", PassWord)//密码,可以要求使用特定加密技术加密
//new Claim("Username", "Admin"),//其他荷载信息
};
// 2. 从 appsettings.json 中读取密钥SecretKey
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWTToken:SecretKey"]));
// 3. 选择加密算法
var algorithm = SecurityAlgorithms.HmacSha256;
// 4. 生成Credentials
var signingCredentials = new SigningCredentials(secretKey, algorithm);
// 5. 根据以上,生成token
var Token = new JwtSecurityToken(
issuer: _configuration["JWTToken:Issuer"], //发布者
audience: _configuration["JWTToken:Audience"], //接收者
claims: claims, //存放的用户信息
notBefore: DateTime.Now, //发布时间
expires: System.DateTime.Now.AddHours(48), //有效期设置为48小时
signingCredentials: signingCredentials //数字签名,用于生成Token的Header,其余都是荷载数据
);
// 6. 将token变为string
var token = new JwtSecurityTokenHandler().WriteToken(Token);
return token;
}
}
5.这时可以生成Token了,我们来新建一个控制器来生成一个试试:
[Route("api/[controller]")]
[ApiController]
public class GetTokenController : ControllerBase
{
private readonly JwtHelper _jwtHelper;
public GetTokenController(JwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
}
[HttpPost]
[Route("Token")]
public Task<JsonResult> GetToken(UserToken token)
{
string thetoken = _jwtHelper.CreateLoginToken(token.Name, "User", token.AppId, token.Account, token.PassWord);
var result = new
{
success = true,
msg = "OK",//消息
token = thetoken
};
return Task.FromResult(new JsonResult(result));
}
}
控制器的参数类(根据自己需要修改)
public class UserToken
{
/// <summary>
/// 给需要访问本系统的程序的唯一标识
/// </summary>
public string AppId { get; set; } = string.Empty;
/// <summary>
/// 需要访问本系统的程序的名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 分配给需要访问本系统的程序的账号
/// </summary>
public string Account { get; set; } = string.Empty;
/// <summary>
/// 分配给需要访问本系统的程序的密码
/// </summary>
public string PassWord { get; set; } = string.Empty;
}
启动程序测试,生成成功!:
6.既然可以生成Token了,那么就该给控制器授权了,总不能让每个携带Token的用户能访问系统所以的API吧,那样会出现垂直越权的情况,渗透测试过不了哦。
相关标签:Authorize 和 AllowAnonymous
授权方式:介绍三种授权方式(Policy、Role、Scheme)
此处着重说Policy方式,对Role方法感兴趣的可以看我前面的Cookie方式验证。
6.1首先新建一个JwtAuthorizationRequirement类(类名不固定)用于继承IAuthorizationRequirement
public class JwtAuthorizationRequirement : IAuthorizationRequirement
{
//这里可以扩展一些其他的角色或者需要的东西.
//txt参数是在使用策略授权时传入进来的,例如:Authorize(Policy= "MyPolicy")的MyPolicy
public JwtAuthorizationRequirement(string name)
{
Name = name;
}
public string? Name { get; set; }
}
6.2然后新建一个JwtAuthorizationHandler类继承AuthorizationHandler<JwtAuthorizationRequirement>
/// <summary>
/// 检验策略,相当于.NET MVC继承AuthorizeAttribute实现JWT的拦截器的效果。
/// </summary>
public class JwtAuthorizationHandler : AuthorizationHandler<JwtAuthorizationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, JwtAuthorizationRequirement requirement)
{
if (context.User == null)
{
context.Fail();
return Task.CompletedTask;
}
//requirement.Name 就是在添加策略授权时传入的值
string? Account = context.User.Claims.FirstOrDefault(p => p.Type == requirement.Name)?.Value;
string? PassWord = context.User.Claims.FirstOrDefault(p => p.Type == "PassWord")?.Value;
//这里时数据库或者其他方式校验
if (Account=="1234")
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
6.3然后将该策略在Program下注入
//注入授权策略(非必要,仅有需要自定义授权规则时使用)
builder.Services.AddSingleton<IAuthorizationHandler, JwtAuthorizationHandler>();
//账号密码验证策略
builder.Services.AddAuthorization(p =>
{
p.AddPolicy("Account", t =>
{
t.Requirements.Add(new JwtAuthorizationRequirement("Account"));
});
});
6.4 使用:
[Authorize(policy: "Account")]//主要是这个
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
策略授权基本就是这样了。
再贴一个策略授权代码(可以忽略):
/// <summary>
/// 规则授权参数
/// </summary>
public class Permissions
{
/// <summary>
/// 规则受体
/// </summary>
public const string User = "User";
/// <summary>
/// 增权限
/// </summary>
public const string UserCreate = User + ".Create";
/// <summary>
/// 删权限
/// </summary>
public const string UserDelete = User + ".Delete";
/// <summary>
/// 改权限
/// </summary>
public const string UserUpdate = User + ".Update";
/// <summary>
/// 查权限
/// </summary>
public const string UserSelect = User + ".Select";
}
public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
/// <summary>
/// 参数是在使用策略授权时传入进来的,例如:Authorize(Policy= "MyPolicy")的MyPolicy
/// </summary>
/// <param name="name">Authorize(Policy= "MyPolicy")的MyPolicy</param>
public PermissionAuthorizationRequirement(string name)
{
Name = name;
}
public string Name { get; set; }
}
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement)
{
//取出当前用户的所有Permission的参数
var permissions = context.User.Claims.Where(_ => _.Type == "Permission").Select(_ => _.Value).ToList();
//是否满足授权,满足则运行 context.Succeed
if (permissions.Any(_ => _.StartsWith(requirement.Name)))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
Program
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
//控制器方法验证策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(Permissions.UserCreate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserCreate)));
options.AddPolicy(Permissions.UserUpdate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserUpdate)));
options.AddPolicy(Permissions.UserDelete, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserDelete)));
options.AddPolicy(Permissions.UserSelect, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserSelect)));
});
7.最后.整个Program
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();
//首先引用Microsoft.AspNetCore.Mvc.NewtonsoftJson包。再在builder.Services.AddControllers()后添加相应内容
builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
//设置JSON返回数据格式大小写与Model一致
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
//设置一般API的日期格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
#region JWT
//获取配置文件
var configuration = builder.Configuration;
string Issuer = configuration["JWTToken:Issuer"];
string Audience = configuration["JWTToken:Audience"];
string SecretKey = configuration["JWTToken:SecretKey"];
//注入jwt
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
//过期时间容错值,解决服务器端时间不同步问题(秒)
//允许服务器时间偏移量30秒,即我们配置的过期时间加上这个允许偏移的时间值,才是真正过期的时间(过期时间 + 偏移值)你也可以设置为0,ClockSkew = TimeSpan.Zero
ClockSkew = TimeSpan.FromSeconds(30),
//要求Token的Claims中必须包含Expires
RequireExpirationTime = true,
//是否在令牌期间验证签发者
ValidateIssuer = true,
//发行人Issuer
ValidIssuer = Issuer,
//是否验证接收者
ValidateAudience = true,
//是否验证失效时间
ValidateLifetime = true,
//是否验证签名SecurityKey
ValidateIssuerSigningKey = true,
//接收者
ValidAudience = Audience,
//密钥SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)),
};
});
//注入JwtHelper
builder.Services.AddSingleton(new JwtHelper(configuration));
//注入授权策略(非必要,仅有需要自定义授权规则时使用)
builder.Services.AddSingleton<IAuthorizationHandler, JwtAuthorizationHandler>();
//账号密码验证策略
builder.Services.AddAuthorization(p =>
{
p.AddPolicy("Account", t =>
{
t.Requirements.Add(new JwtAuthorizationRequirement("Account"));
});
});
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
//控制器方法验证策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(Permissions.UserCreate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserCreate)));
options.AddPolicy(Permissions.UserUpdate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserUpdate)));
options.AddPolicy(Permissions.UserDelete, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserDelete)));
options.AddPolicy(Permissions.UserSelect, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserSelect)));
});
#endregion
//注入Swagger
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference=new OpenApiReference{Id="Bearer",Type=ReferenceType.SecurityScheme},
},
Array.Empty<string>()
}
});
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "请输入文字'Bearer '后面跟空格和token格式 Bearer {token}",
Name = "Authorization",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey
});
});
//配置跨域
builder.Services.AddCors(policy =>
{
policy.AddPolicy("CorsPolicy", opt => opt
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("X-Pagination"));
});
var app = builder.Build();
//调用中间件:UseAuthentication(认证),
//必须在所有需要身份认证的中间件前调用,比如 UseAuthorization(授权)。
app.UseAuthentication();
//调用中间件:UseAuthorization(授权)。
app.UseAuthorization();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();