授权和鉴权分为了两个项目。
首先是授权:
- 建立Asp.net core 项目,并在Nuget包安装 System.IdentityModel.Tokens.Jwt
- 新建一个Web Api 用于登录,这里使用账户密码方便调试。另外BaseResult是我封装的一个统一返回数据类型。需要注意的是 audience 以及 JwtRegisteredClaimNames.Name 都不应该固定写死(如果有三方或多客户端登录等...),我这里只是方便调试。具体请到Jwt查看相关资料。
///
/// 账户密码登录 /// /// ///[HttpPost] public BaseResult Post(LoginModel loginModel) { //检查账户密码 Users user = usersManage.GetUserByAccountAndPwd(loginModel.AccountName, loginModel.Password); if (user == null) { return new BaseResult { result = "error", code = E_ResultCode.error, message = "账号名或密码错误" }; } var claims = new List { new Claim(JwtRegisteredClaimNames.Name, "ZxManagementPlatform"), new Claim("ID",user.ID) //此处可以自定义添加所需要的数据... }; claims.AddRange(roles.Select(x => new Claim("Role", x.RoleCode))); //===================================================================== //对称 //var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalConfig.JwtSecurityKey)); //===================================================================== //非对称 var rsa = RSA.Create(); byte[] publicKey = Convert.FromBase64String("公钥Base64(掐头去尾,不带Begin...以及回车空格等格式的) ---这里其实不需要公钥,但是可以作为保存"); byte[] privateKey = Convert.FromBase64String("私钥Base64(掐头去尾,不带Begin...以及回车空格等格式的) ---我是用的是 Pkcs8格式的密钥"); rsa.ImportPkcs8PrivateKey(privateKey, out _); var key = new RsaSecurityKey(rsa); PrivateKeyStatus privateKeyStatus = key.PrivateKeyStatus; //===================================================================== var token = new JwtSecurityToken( issuer: GlobalConfig.JwtIssuer, audience: "zxservice", claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddHours(2), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.RsaSha256) //signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) ); var jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return new BaseResult { result = "success", code = E_ResultCode.success, message = "成功", data = jwtToken }; }
然后是鉴权
- 建立Asp.net core 项目,并在Nuget包安装 Microsoft.AspNetCore.Authentication.JwtBearer
- 添加自定义身份校验代码
- 这个部分参考于 https://blog.csdn.net/weixin_33854644/article/details/85018389 ,并修改了一部分。
- 首先建立文件夹JwtPolicy 并添加文件和代码
//================================================================================== //============================ 文件一 ////// 用户或角色或其他凭据实体 /// public class Permission { ////// 用户或角色或其他凭据名称 /// public virtual string Name { get; set; } ////// 请求Url /// public virtual string Url { get; set; } } //================================================================================== //============================ 文件二 ////// 权限授权Handler /// public class PermissionHandler : AuthorizationHandler{ /// /// 验证方案提供对象 /// public IAuthenticationSchemeProvider Schemes { get; set; } ////// 自定义策略参数 /// public PermissionRequirement Requirement { get; set; } ////// 没有权限,禁止访问 /// private static readonly string ForbiddenResult = JsonConvert.SerializeObject(new BaseResult { code = E_ResultCode.Forbidden, result = "error", message = "没有权限,禁止访问" }); ////// 没有登录 /// private static readonly string UnauthorizedResult = JsonConvert.SerializeObject(new BaseResult { code = E_ResultCode.Unauthorized, result = "error", message = "请先登录" }); ////// 构造 /// /// public PermissionHandler(IAuthenticationSchemeProvider schemes) { Schemes = schemes; } ////// 权限校验 /// /// /// ///protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { //赋值用户权限 Requirement = requirement; //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息 var httpContext = (context.Resource as Microsoft.AspNetCore.Http.DefaultHttpContext).HttpContext; //==================================================================================== //请求Url var questUrl = httpContext.Request.Path.Value.ToLower(); //请求的方式 var questMethod = httpContext.Request.Method.ToLower(); //判断请求是否停止 var handlers = httpContext.RequestServices.GetRequiredService (); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler; if (handler != null && await handler.HandleRequestAsync()) { context.Fail(); return; } } //判断请求是否拥有凭据,即有没有登录 var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); //result?.Principal不为空 并且 Succeeded == true 即登录成功 if (result?.Succeeded == true && result?.Principal != null) { httpContext.User = result.Principal; //权限中是否存在请求的url if (Requirement.Permissions.Where(x => questUrl.ToLower().StartsWith(x.Url.ToLower())).Any()) { List LtRoles = httpContext.User.Claims.Where(s => s.Type == requirement.ClaimType).Select(x => x.Value).ToList(); //验证权限 if (!Requirement.Permissions.Where(w => LtRoles.Contains(w.Name) && questUrl.ToLower().StartsWith(w.Url.ToLower()) && w.Method.ToLower() == questMethod).Any()) { //无权限拒绝访问 httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsync(ForbiddenResult); context.Fail(); } } context.Succeed(requirement); return; } else { //没有登录 httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsync(UnauthorizedResult); context.Fail(); } } //判断没有登录时,是否访问登录的url,并且是Post请求,并助是form表单提交类型,否则为失败 if (!questUrl.Equals(Requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType)) { //没有登录 httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsync(UnauthorizedResult); context.Fail(); } context.Succeed(requirement); } } //================================================================================== //============================ 文件三 /// /// 必要参数类 /// public class PermissionRequirement : IAuthorizationRequirement { ////// 用户权限集合 /// public ListPermissions { get; private set; } /// /// 无权限action /// public string DeniedAction { get; set; } ////// 认证授权类型 /// public string ClaimType { internal get; set; } ////// 请求路径 /// public string LoginPath { get; set; } = "/Api/Login"; ////// 发行人 /// public string Issuer { get; set; } ////// 订阅人 /// public string Audience { get; set; } ////// 过期时间 /// public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5000); ////// 签名验证 /// public SigningCredentials SigningCredentials { get; set; } /// 构造 /// /// 拒约请求的url /// 权限集合 /// 声明类型 /// 发行人 /// 订阅人 /// 签名验证实体 public PermissionRequirement(string deniedAction, Listpermissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials) { ClaimType = claimType; DeniedAction = deniedAction; Permissions = permissions; Issuer = issuer; Audience = audience; SigningCredentials = signingCredentials; } }
- 在Startup中的 ConfigureServices 方法中注册服务。其中 rsa.ImportPkcs8PublicKey 这是一个扩展方法,来源于 RSAExtensions 包,大家看一下这位大哥的Github https://github.com/stulzq/RSAExtensions 。这个包提供了导入 pkcs8 格式公钥的方法,以及其他好用的方法。
public void ConfigureServices(IServiceCollection services) { #region 其他服务注册 ... #endregion //添加JWT验证 对称密钥 --- 弃用 //services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // .AddJwtBearer(option => // { // option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters // { // ValidateIssuer = true, // ValidateAudience = true, // ValidateLifetime = true, // ValidateIssuerSigningKey = true, // ValidAudience = "zxservice", // ValidIssuer = GlobalConfig.JwtIssuer, // IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalConfig.JwtSecurityKey)), // }; // }); //================================================================================================================ // 自定义权限校 //对称密钥 //var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalConfig.JwtSecurityKey)), SecurityAlgorithms.HmacSha256); //非对称密钥 var rsa = RSA.Create(); byte[] publicKey = Convert.FromBase64String("公钥Base64(掐头去尾,不带Begin...以及回车空格等格式的)"); //rsa.ImportPkcs8PublicKey 这是一个扩展方法,来源于 RSAExtensions 包,大家可以关注一下这位大哥的Github https://github.com/stulzq/RSAExtensions 。这个包提供了导入 pkcs8 格式公钥的方法。 rsa.ImportPkcs8PublicKey(publicKey); var sKey = new RsaSecurityKey(rsa); //非对称密钥 var signingCredentials = new SigningCredentials(sKey, SecurityAlgorithms.RsaPKCS1); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingCredentials.Key, ValidateIssuer = true, ValidIssuer = GlobalConfig.JwtIssuer, ValidateAudience = true, ValidAudience = GlobalConfig.JwtAudience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; services.AddAuthorization(option => { //这个集合模拟用户权限表,可从数据库中查询出来 var permission = new List{ new Permission { Url="/api/v1/Test", Name="supadmin", Method="get" }, new Permission { Url="/api/v1/MyBasicInfo", Name="supadmin", Method="get" }, }; var permissionRequirement = new PermissionRequirement("", permission, "Role", GlobalConfig.JwtIssuer, GlobalConfig.JwtAudience, signingCredentials); //添加自定义验证规则 option.AddPolicy("ZxPermission", policy => policy.Requirements.Add(permissionRequirement)); }).AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(o => { o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidAudience = GlobalConfig.JwtAudience, ValidIssuer = GlobalConfig.JwtIssuer, IssuerSigningKey = signingCredentials.Key, }; }); services.AddSingleton (); //================================================================================================================ }
测试
- 首先在 swagger 中调用api获取 token
- 然后用 PostMan 模拟请求并带上Token 注意 Type 要选择 Bearer Token
- 其实也可以访问 https://jwt.io/ 校验Token 。不过需要注意的是,在https://jwt.io/ 上输入公钥时 必须带上 -----BEGIN PUBLIC KEY----- 以及 -----END PUBLIC KEY----- 不然无法验签。
***注意*** 以上文章仅作为个人学习记录,文中的方案有很多还未详细处理。因为怕日后忘了,所以先记录下来。如有错误还请指正 谢谢!



