Asp.Net Core基于JWT认证的数据接口网关实例代码

时间:2021-05-28

前言

近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo。朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对前后端分离的数据服务支持,于是想到我一直做.Net开发,问我是否对.Net Core有所了解?能不能做个简单Demo出来看看?我说,分道扬镳之后我不是调用别人的接口就是提供接口给别人调用,于是便有了以下示例代码。

示例要求能演示获取Token及如何使用该Token访问数据资源,在Demo中实现了JWT的颁发及验证以及重写一个ActionAuthorizeAttribute实现对具体数据接口的调用权限控制,先看一下项目截图:

[项目截图]

项目文件介绍

解决方案下只有一个项目,项目名称就叫Jwt.Gateway,包含主要文件有:

  • Controllers目录下的ApiActionFilterAttribute.cs文件,继承Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute,用于校验接口调用者对具体接口的访问权限。
  • Controllers目录下的ApiBase.cs文件,继承Microsoft.AspNetCore.Mvc.Controller,具有Microsoft.AspNetCore.Authorization.Authorize特性引用,用于让所有数据接口用途的控制器继承,定义有CurrentAppKey属性(来访应用程序的身份标识)并在OnActionExecuting事件中统一分析Claims并赋值。
  • Controllers目录下的TokenController.cs控制器文件,用于对调用方应用程序获取及注销Token。
  • Controllers目录下的UsersController.cs控制器文件,继承ApiBase.cs,作为数据调用示例。
  • MiddleWares目录下的ApiCustomException.cs文件,是一个数据接口的统一异常处理中间件。
  • Models目录下的ApiResponse.cs文件,用于做数据接口的统一数据及错误信息输出实体模型。
  • Models目录下的User.cs文件,示例数据实体模型。
  • Program.cs及Startup.cs文件就不介绍了,随便建个空项目都有。
  • 项目文件代码

    ApiActionFilterAttribute.cs

    Controllers目录下的ApiActionFilterAttribute.cs文件,继承Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute,用于校验接口调用者对具体接口的访问权限。

    设想每一个到访的请求都是一个应用程序,每一个应用程序都分配有基本的Key和Password,每一个应用程序具有不同的接口访问权限,所以在具体的数据接口上应该声明该接口所要求的权限值,比如修改用户信息的接口应该在接口方法上声明需要具有“修改用户”的权限,用例: [ApiActionFilter("用户修改")] 。

    大部分情况下一个接口(方法)对应一个操作,这样基本上就能应付了,但是不排除有时候可能需要多个权限组合进行验证,所以该文件中有一个对多个权限值进行校验的“与”和“和”枚举,用例: [ApiActionFilter(new string[] { "用户修改", "用户录入", "用户删除" },ApiActionFilterAttributeOption.AND)] ,这样好像就差不多了。

    由于在一个接口调用之后可能需要将该接口所声明需要的权限值记入日志等需求,因此权限值集合将被写入到HttpContext.Items["Permissions"]中以方便可能的后续操作访问,看代码:

    using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc.Filters;namespace Jwt.Gateway.Controllers{ public enum ApiActionFilterAttributeOption { OR,AND } public class ApiActionFilterAttribute : Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute { List<string> Permissions = new List<string>(); ApiActionFilterAttributeOption Option = ApiActionFilterAttributeOption.AND; public ApiActionFilterAttribute(string permission) { Permissions.Add(permission); } public ApiActionFilterAttribute(string[] permissions, ApiActionFilterAttributeOption option) { foreach(var permission in permissions) { if (Permissions.Contains(permission)) { continue; } Permissions.Add(permission); } Option = option; } public override void OnActionExecuting(ActionExecutingContext context) { var key = GetAppKey(context); List<string> keyPermissions = GetAppKeyPermissions(key); var isAnd = Option == ApiActionFilterAttributeOption.AND; var permissionsCount = Permissions.Count; var keyPermissionsCount = keyPermissions.Count; for (var i = 0; i < permissionsCount; i++) { bool flag = false; for (var j = 0; j < keyPermissions.Count; j++) { if (flag = string.Equals(Permissions[i], keyPermissions[j], StringComparison.OrdinalIgnoreCase)) { break; } } if (flag) { continue; } if (isAnd) { throw new Exception("应用“" + key + "”缺少“" + Permissions[i] + "”的权限"); } } context.HttpContext.Items.Add("Permissions", Permissions); base.OnActionExecuting(context); } private string GetAppKey(ActionExecutingContext context) { var claims = context.HttpContext.User.Claims; if (claims == null) { throw new Exception("未能获取到应用标识"); } var claimKey = claims.ToList().Find(o => string.Equals(o.Type, "AppKey", StringComparison.OrdinalIgnoreCase)); if (claimKey == null) { throw new Exception("未能获取到应用标识"); } return claimKey.Value; } private List<string> GetAppKeyPermissions(string appKey) { List<string> li = new List<string> { "用户明细","用户列表","用户录入","用户修改","用户删除" }; return li; } }}ApiActionAuthorizeAttribute.cs

    ApiBase.cs

    Controllers目录下的ApiBase.cs文件,继承Microsoft.AspNetCore.Mvc.Controller,具有Microsoft.AspNetCore.Authorization.Authorize特性引用,用于让所有数据接口用途的控制器继承,定义有CurrentAppKey属性(来访应用程序的身份标识)并在OnActionExecuting事件中统一分析Claims并赋值。

    通过验证之后,Aps.Net Core会在HttpContext.User.Claims中将将来访者的身份信息记录下来,我们可以通过该集合得到来访者的身份信息。

    using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;namespace Jwt.Gateway.Controllers{ [Microsoft.AspNetCore.Authorization.Authorize] public class ApiBase : Microsoft.AspNetCore.Mvc.Controller { private string _CurrentAppKey = ""; public string CurrentAppKey { get { return _CurrentAppKey; } } public override void OnActionExecuting(ActionExecutingContext context) { var claims = context.HttpContext.User.Claims.ToList(); var claim = claims.Find(o => o.Type == "appKey"); if (claim == null) { throw new Exception("未通过认证"); } var appKey = claim.Value; if (string.IsNullOrEmpty(appKey)) { throw new Exception("appKey不合法"); } _CurrentAppKey = appKey; base.OnActionExecuting(context); } }}ApiBase.cs

    TokenController.cs

    Controllers目录下的TokenController.cs控制器文件,用于对调用方应用程序获取及注销Token。

    using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;namespace Jwt.Gateway.Controllers{ [Route("api/[controller]/[action]")] public class TokenController : Controller { private readonly Microsoft.Extensions.Configuration.IConfiguration _configuration; public TokenController(Microsoft.Extensions.Configuration.IConfiguration configuration) { _configuration = configuration; } // /api/token/get public IActionResult Get(string appKey, string appPassword) { try { if (string.IsNullOrEmpty(appKey)) { throw new Exception("缺少appKey"); } if (string.IsNullOrEmpty(appKey)) { throw new Exception("缺少appPassword"); } if (appKey != "myKey" && appPassword != "myPassword")//固定的appKey及appPassword,实际项目中应该来自数据库或配置文件 { throw new Exception("配置不存在"); } var key = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"])); var creds = new Microsoft.IdentityModel.Tokens.SigningCredentials(key, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256); var claims = new List<System.Security.Claims.Claim>(); claims.Add(new System.Security.Claims.Claim("appKey", appKey));//仅在Token中记录appKey var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken( issuer: _configuration["JwtTokenIssuer"], audience: _configuration["JwtTokenAudience"], claims: claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); return Ok(new Models.ApiResponse { status = 1, message = "OK", data = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler().WriteToken(token) }); } catch(Exception ex) { return Ok(new Models.ApiResponse { status = 0, message = ex.Message, data = "" }); } } // /api/token/delete public IActionResult Delete(string token) { //code: 加入黑名单,使其无效 return Ok(new Models.ApiResponse { status = 1, message = "OK", data = "" }); } }}TokenController.cs

    UsersController.cs

    Controllers目录下的UsersController.cs控制器文件,继承ApiBase.cs,作为数据调用示例。

    该控制器定义了对User对象常规的明细、列表、录入、修改、删除等操作。

    using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc;namespace Jwt.Gateway.Controllers{ [Produces("application/json")] [Route("api/[controller]/[action]")] public class UsersController : ApiBase { /* * 1.要访问访问该控制器提供的接口请先通过"/api/token/get"获取token * 2.访问该控制器提供的接口http请求头必须具有值为"Bearer+空格+token"的Authorization键,格式参考: * "Authorization"="Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQXBwIiwiYXBwS2V5IjoibXlLZXkiLCJleHAiOjE1NTE3ODc2MDMsImlzcyI6IkdhdGV3YXkiLCJhdWQiOiJhdWRpZW5jZSJ9.gQ9_Q7HUT31oFyfl533T-bNO5IWD2drl0NmD1JwQkMI" */ /// <summary> /// 临时用户测试数据,实际项目中应该来自数据库等媒介 /// </summary> static List<Models.User> _Users = null; static object _Lock = new object(); public UsersController() { if (_Users == null) { lock (_Lock) { if (_Users == null) { _Users = new List<Models.User>(); var now = DateTime.Now; for(var i = 0; i < 10; i++) { var num = i + 1; _Users.Add(new Models.User { UserId = num, UserName = "name"+num, UserPassword = "pwd"+num, UserJoinTime = now }); } } } } } // /api/users/detail [ApiActionFilter("用户明细")] public IActionResult Detail(long userId) { var user = _Users.Find(o => o.UserId == userId); if (user == null) { throw new Exception("用户不存在"); } return Ok(new Models.ApiResponse { data = user, status = 1, message = "OK" }); } // /api/users/list [ApiActionFilter("用户列表")] public IActionResult List(int page, int size) { page = page < 1 ? 1 : page; size = size < 1 ? 1 : size; var total = _Users.Count(); var pages = total % size == 0 ? total / size : ((long)Math.Floor((double)total / size + 1)); if (page > pages) { return Ok(new Models.ApiResponse { data = new List<Models.User>(), status = 1, message = "OK", total = total }); } var li = new List<Models.User>(); var startIndex = page * size - size; var endIndex = startIndex + size - 1; if (endIndex > total - 1) { endIndex = total - 1; } for(; startIndex <= endIndex; startIndex++) { li.Add(_Users[startIndex]); } return Ok(new Models.ApiResponse { data = li, status = 1, message = "OK", total = total }); } // /api/users/add [ApiActionFilter("用户录入")] public IActionResult Add() { return Ok(new Models.ApiResponse { status = 1, message = "OK" }); } // /api/users/update [ApiActionFilter(new string[] { "用户修改", "用户录入", "用户删除" },ApiActionFilterAttributeOption.AND)] public IActionResult Update() { return Ok(new Models.ApiResponse { status = 1, message = "OK" }); } // /api/users/delete [ApiActionFilter("用户删除")] public IActionResult Delete() { return Ok(new Models.ApiResponse { status = 1, message = "OK" }); } }}UsersController.cs

    ApiCustomException.cs

    MiddleWares目录下的ApiCustomException.cs文件,是一个数据接口的统一异常处理中间件。

    该文件整理并抄袭自:https://pletedTask; } }; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidAudience = _configuration["JwtTokenAudience"], ValidIssuer = _configuration["JwtTokenIssuer"], IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"])) }; }); services.AddMvc().AddJsonOptions(option=> { option.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss.fff"; }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseApiCustomException(new ApiCustomExceptionMiddleWareOption( handleType: ApiCustomExceptionHandleType.Both, jsonHandleUrlKeys: new PathString[] { "/api" }, errorHandingPath: "/home/error")); app.UseAuthentication(); app.UseMvc(); } bool InBlacklist(string token) { //code: 实际项目中应该查询数据库或配置文件进行比对 return false; } }}Startup.cs

    Program.cs

    using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore;using Microsoft.AspNetCore.Hosting;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.Logging;namespace Jwt.Gateway{ public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true) .Build(); return WebHost.CreateDefaultBuilder(args) .UseKestrel() .UseConfiguration(config) .UseStartup<Startup>() .Build(); } }}Program.cs

    运行截图

    [运行截图-获取Token]

    [运行截图-配置Fiddler调用接口获取数据]

    [运行截图-获取到数据]

    如果Token校验失败将会返回401错误!

    总结

    以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章