[开源]WebApi 快速构建示例

  1. 实现代码:MasterChief.DotNet.ProjectTemplate.WebApi
  2. Demo Code:https://github.com/YanZhiwei/MasterChief.ProjectTemplate.WebApiSample
  3. Nuget : Install-Package MasterChief.DotNet.ProjectTemplate.WebApi
  4. 实现WebApi开发中诸如授权验证,缓存,参数验证,异常处理等,方便快速构建项目而无需过多关心技术细节;
  5. 欢迎Star,欢迎PR;

目录

  • 授权
  • 鉴权
  • 授权与鉴权使用
  • 基于请求缓存处理
  • 异常处理
  • 参数验证

Created by gh-md-toc

授权

  1. 授权接口,通过该接口自定义授权实现,项目默认实现基于Jwt授权

    /// <summary>
    ///     WebApi 授权接口
    /// </summary>
    public interface IApiAuthorize
    {
        /// <summary>
        ///     检查请求签名合法性
        /// </summary>
        /// <param name="signature">加密签名字符串</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <param name="appConfig">应用接入配置信息</param>
        /// <returns>CheckResult</returns>
        CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig);
    
        /// <summary>
        ///     创建合法用户获取访问令牌接口数据
        /// </summary>
        /// <param name="identityUser">IdentityUser</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns>IdentityToken</returns>
        ApiResult<IdentityToken> CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig);
    }
  2. 基于Jwt授权实现
    /// <summary>
    ///     基于Jwt 授权实现
    /// </summary>
    public sealed class JwtApiAuthorize : IApiAuthorize
    {
        /// <summary>
        ///     检查请求签名合法性
        /// </summary>
        /// <param name="signature">加密签名字符串</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <param name="appConfig">应用接入配置信息</param>
        /// <returns>CheckResult</returns>
        public CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig)
        {
            ValidateOperator.Begin()
                .NotNullOrEmpty(signature, "加密签名字符串")
                .NotNullOrEmpty(timestamp, "时间戳")
                .NotNullOrEmpty(nonce, "随机数")
                .NotNull(appConfig, "AppConfig");
            var appSecret = appConfig.AppSecret;
            var signatureExpired = appConfig.SignatureExpiredMinutes;
            string[] data = {appSecret, timestamp, nonce};
            Array.Sort(data);
            var signatureText = string.Join("", data);
            signatureText = Md5Encryptor.Encrypt(signatureText);
    
            if (!signature.CompareIgnoreCase(signatureText) && CheckHelper.IsNumber(timestamp))
                return CheckResult.Success();
            var timestampMillis =
                UnixEpochHelper.DateTimeFromUnixTimestampMillis(timestamp.ToDoubleOrDefault());
            var minutes = DateTime.UtcNow.Subtract(timestampMillis).TotalMinutes;
    
            return minutes > signatureExpired ? CheckResult.Fail("签名时间戳失效") : CheckResult.Success();
        }
    
        /// <summary>
        ///     创建合法用户获取访问令牌接口数据
        /// </summary>
        /// <param name="identityUser">IdentityUser</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns>IdentityToken</returns>
        public ApiResult<IdentityToken> CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig)
        {
            ValidateOperator.Begin()
                .NotNull(identityUser, "IdentityUser")
                .NotNull(appConfig, "AppConfig");
            var payload = new Dictionary<string, object>
            {
                {"iss", identityUser.UserId},
                {"iat", UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds}
            };
            var identityToken = new IdentityToken
            {
                AccessToken = CreateIdentityToken(appConfig.SharedKey, payload),
                ExpiresIn = appConfig.TokenExpiredDay * 24 * 3600
            };
            return ApiResult<IdentityToken>.Success(identityToken);
        }
    
        /// <summary>
        ///     创建Token
        /// </summary>
        /// <param name="secret">密钥</param>
        /// <param name="payload">负载数据</param>
        /// <returns>Token令牌</returns>
        public static string CreateIdentityToken(string secret, Dictionary<string, object> payload)
        {
            ValidateOperator.Begin().NotNull(payload, "负载数据").NotNullOrEmpty(secret, "密钥");
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
            return encoder.Encode(payload, secret);
        }
    }

鉴权

  1. Token令牌鉴定接口,通过该接口可以自定义扩展实现方式,项目默认实现基于Jwt鉴权

    /// <summary>
    ///     webApi 验证系统基本接口
    /// </summary>
    public interface IApiAuthenticate
    {
        #region Methods
    
        /// <summary>
        ///     验证Token令牌是否合法
        /// </summary>
        /// <param name="token">令牌</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns>CheckResult</returns>
        ApiResult<string> CheckIdentityToken(string token, AppConfig appConfig);
    
        #endregion Methods
    }
  2. 基于Jwt鉴权实现
    /// <summary>
    ///     基于Jwt 授权验证实现
    /// </summary>
    public sealed class JwtApiAuthenticate : IApiAuthenticate
    {
        /// <summary>
        ///     检查Token是否合法
        /// </summary>
        /// <param name="token">用户令牌</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns></returns>
        public ApiResult<string> CheckIdentityToken(string token, AppConfig appConfig)
        {
            ValidateOperator.Begin()
                .NotNullOrEmpty(token, "Token")
                .NotNull(appConfig, "AppConfig");
            try
            {
                var tokenText = ParseTokens(token, appConfig.SharedKey);
                if (string.IsNullOrEmpty(tokenText))
                    return ApiResult<string>.Fail("用户令牌Token为空");
    
                dynamic root = JObject.Parse(tokenText);
                string userid = root.iss;
                double iat = root.iat;
                var validTokenExpired =
                    new TimeSpan((int) (UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds - iat))
                        .TotalDays > appConfig.TokenExpiredDay;
                return validTokenExpired
                    ? ApiResult<string>.Fail($"用户ID{userid}令牌失效")
                    : ApiResult<string>.Success(userid);
            }
            catch (FormatException)
            {
                return ApiResult<string>.Fail("用户令牌非法");
            }
            catch (SignatureVerificationException)
            {
                return ApiResult<string>.Fail("用户令牌非法");
            }
        }
    
        /// <summary>
        ///     转换Token
        /// </summary>
        /// <param name="token">令牌</param>
        /// <param name="secret">密钥</param>
        /// <returns>Token以及负载数据</returns>
        private string ParseTokens(string token, string secret)
        {
            ValidateOperator.Begin()
                .NotNullOrEmpty(token, "令牌")
                .NotNullOrEmpty(secret, "密钥");
    
            IJsonSerializer serializer = new JsonNetSerializer();
            IDateTimeProvider provider = new UtcDateTimeProvider();
            IJwtValidator validator = new JwtValidator(serializer, provider);
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
            return decoder.Decode(token, secret, true);
        }
    }

授权与鉴权使用

  1. 授权使用,通过Controller构造函数方式,代码如下

    /// <summary>
    ///     Api授权
    /// </summary>
    public abstract class AuthorizeController : ApiBaseController
    {
        #region Constructors
    
        /// <summary>
        ///     构造函数
        /// </summary>
        /// <param name="apiAuthorize">IApiAuthorize</param>
        /// <param name="appCfgService">IAppConfigService</param>
        protected AuthorizeController(IApiAuthorize apiAuthorize, IAppConfigService appCfgService)
        {
            ValidateOperator.Begin()
                .NotNull(apiAuthorize, "IApiAuthorize")
                .NotNull(appCfgService, "IAppConfigService");
            ApiAuthorize = apiAuthorize;
            AppCfgService = appCfgService;
        }
    
        #endregion Constructors
    
        #region Fields
    
        /// <summary>
        ///     授权接口
        /// </summary>
        protected readonly IApiAuthorize ApiAuthorize;
    
        /// <summary>
        ///     请求通道配置信息,可以从文件或者数据库获取
        /// </summary>
        protected readonly IAppConfigService AppCfgService;
    
        #endregion Fields
    
        #region Methods
    
        /// <summary>
        ///     创建合法用户的Token
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="passWord">用户密码</param>
        /// <param name="signature">加密签名字符串</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <param name="appid">应用接入ID</param>
        /// <returns>OperatedResult</returns>
        protected virtual ApiResult<IdentityToken> CreateIdentityToken(string userId, string passWord,
            string signature, string timestamp,
            string nonce, Guid appid)
        {
            #region  参数检查
    
            var checkResult = CheckRequest(userId, passWord, signature, timestamp, nonce, appid);
    
            if (!checkResult.State)
                return ApiResult<IdentityToken>.Fail(checkResult.Message);
    
            #endregion
    
            #region 用户鉴权
    
            var getIdentityUser = GetIdentityUser(userId, passWord);
    
            if (!getIdentityUser.State) return ApiResult<IdentityToken>.Fail(getIdentityUser.Message);
    
            #endregion
    
            #region 请求通道检查
    
            var getAppConfig = AppCfgService.Get(appid);
    
            if (!getAppConfig.State) return ApiResult<IdentityToken>.Fail(getAppConfig.Message);
            var appConfig = getAppConfig.Data;
    
            #endregion
    
            #region 检查请求签名检查
    
            var checkSignatureResult = ApiAuthorize.CheckRequestSignature(signature, timestamp, nonce, appConfig);
            if (!checkSignatureResult.State) return ApiResult<IdentityToken>.Fail(checkSignatureResult.Message);
    
            #endregion
    
            #region 生成基于Jwt Token
    
            var getTokenResult = ApiAuthorize.CreateIdentityToken(getIdentityUser.Data, getAppConfig.Data);
            if (!getTokenResult.State) return ApiResult<IdentityToken>.Fail(getTokenResult.Message);
    
            return ApiResult<IdentityToken>.Success(getTokenResult.Data);
    
            #endregion
        }
    
        /// <summary>
        ///     检查用户的合法性
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="passWord">用户密码</param>
        /// <returns>UserInfo</returns>
        protected abstract CheckResult<IdentityUser> GetIdentityUser(string userId, string passWord);
    
        private CheckResult CheckRequest(string userId, string passWord, string signature, string timestamp,
            string nonce, Guid appid)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(passWord))
                return CheckResult.Fail("用户名或密码为空");
    
            if (string.IsNullOrEmpty(signature))
                return CheckResult.Fail("请求签名为空");
    
            if (string.IsNullOrEmpty(timestamp))
                return CheckResult.Fail("时间戳为空");
    
            if (string.IsNullOrEmpty(nonce))
                return CheckResult.Fail("随机数为空");
    
            if (appid == Guid.Empty)
                return CheckResult.Fail("应用接入ID非法");
    
            return CheckResult.Success();
        }
    
        #endregion Methods
    }
  2. 鉴权使用,通过AuthorizationFilterAttribute形式,标注请求是否需要鉴权
    /// <summary>
     ///     WebApi 授权验证实现
     /// </summary>
     [AttributeUsage(AttributeTargets.Method)]
     public abstract class AuthenticateAttribute : AuthorizationFilterAttribute
     {
         #region Constructors
    
         /// <summary>
         ///     构造函数
         /// </summary>
         /// <param name="apiAuthenticate">IApiAuthenticate</param>
         /// <param name="appCfgService">appCfgService</param>
         protected AuthenticateAttribute(IApiAuthenticate apiAuthenticate, IAppConfigService appCfgService)
         {
             ValidateOperator.Begin()
                 .NotNull(apiAuthenticate, "IApiAuthenticate")
                 .NotNull(appCfgService, "IAppConfigService");
             ApiAuthenticate = apiAuthenticate;
             AppCfgService = appCfgService;
         }
    
         #endregion Constructors
    
         #region Fields
    
         /// <summary>
         ///     授权验证接口
         /// </summary>
         protected readonly IApiAuthenticate ApiAuthenticate;
    
         /// <summary>
         ///     请求通道配置信息,可以从文件或者数据库获取
         /// </summary>
         protected readonly IAppConfigService AppCfgService;
    
         #endregion Fields
    
         #region Methods
    
         /// <summary>
         ///     验证Token令牌是否合法
         /// </summary>
         /// <param name="token">令牌</param>
         /// <param name="appid">应用ID</param>
         /// <returns>CheckResult</returns>
         protected virtual ApiResult<string> CheckIdentityToken(string token, Guid appid)
         {
             #region 请求参数检查
    
             var checkResult = CheckRequest(token, appid);
    
             if (!checkResult.State)
                 return ApiResult<string>.Fail(checkResult.Message);
    
             #endregion
    
             #region 请求通道检查
    
             var getAppConfig = AppCfgService.Get(appid);
    
             if (!getAppConfig.State) return ApiResult<string>.Fail(getAppConfig.Message);
             var appConfig = getAppConfig.Data;
    
             #endregion
    
             return ApiAuthenticate.CheckIdentityToken(token, appConfig);
         }
    
         private CheckResult CheckRequest(string token, Guid appid)
         {
             if (string.IsNullOrEmpty(token))
                 return CheckResult.Fail("用户令牌为空");
             return Guid.Empty == appid ? CheckResult.Fail("应用ID非法") : CheckResult.Success();
         }
    
         #endregion Methods
     }

基于请求缓存处理

  1. 通过ICacheProvider接口,可以扩展缓存数据方式;
  2. 通过配置DependsOnIdentity参数,可以配置是否依赖Token令牌进行缓存;
  3. 通过配置CacheMinutes参数,可以指定具体接口缓存时间,当设置0的时候不启用缓存;
  4. 通过实现ControllerCacheAttribute,可以在不同项目快速达到接口缓存功能;
    public class RequestCacheAttribute : ControllerCacheAttribute
    {
        public RequestCacheAttribute(int cacheMinutes) : this(cacheMinutes, true, new LocalCacheProvider())
        {
        }
    
        public RequestCacheAttribute(int cacheMinutes, bool dependsOnIdentity, ICacheProvider cacheProvider) : base(
            cacheMinutes, dependsOnIdentity, cacheProvider)
        {
        }
    
        protected override bool CheckedResponseAvailable(HttpActionContext context, string responseText)
        {
            return !string.IsNullOrEmpty(responseText) && context != null;
        }
    
        protected override string GetIdentityToken(HttpActionContext actionContext)
        {
            return actionContext.Request.GetUriOrHeaderValue("Access_token").ToStringOrDefault(string.Empty);
        }
    }

异常处理

  1. 通过实现ControllerExceptionAttribute,可以轻松简单构建接口请求时候异常发生,并通过HttpRequestRaw requestRaw参数,可以获取非常详尽的请求信息;

    public sealed class ExceptionLogAttribute : ControllerExceptionAttribute
    {
        public override void OnActionExceptioning(HttpActionExecutedContext actionExecutedContext, string actionName,
            HttpStatusCode statusCode,
            HttpRequestRaw requestRaw)
        {
            var response = new HttpResponseMessage
            {
                Content = new StringContent("发生故障,请稍后重试!"),
                StatusCode = statusCode
            };
            actionExecutedContext.Response = response;
        }
    }

参数验证

  1. 通过实现ValidateModelAttribute,以及DataAnnotations快速构建请求参数验证
  2. 请求参数只需要DataAnnotations标注即可;
    public sealed class ArticleRequest
    {
        [Required(ErrorMessage = "缺少文章ID")]
        public int Id
        {
            get;
            set;
        }
    
    }
  3. 项目实现ValidateModelAttribute,可以自定义构建参数处理方式
    /// <summary>
    /// 请求参数
    /// </summary>
    public sealed class ValidateRequestAttribute : ValidateModelAttribute
    {
        public override void OnParameterIsNulling(HttpActionContext actionContext)
        {
            actionContext.Response =
                actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult<string>.Fail("请求参数非法。"));
        }
    
        public override void OnParameterInvaliding(HttpActionContext actionContext, ValidationFailedResult result)
        {
            var message = result.Data.FirstOrDefault()?.Message;
            actionContext.Response =
                actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult<string>.Fail(message));
        }
    }

原文地址:https://www.cnblogs.com/MeetYan/p/WebApi.html

时间: 2024-10-15 04:41:15

[开源]WebApi 快速构建示例的相关文章

快速构建ASP.NET MVC Admin主页

前言 后台开发人员一般不喜欢调样式,搞半天样式出不来,还要考虑各种浏览器兼容,费心费力不讨好,还好互联网时代有大量的资源共享,避免我们从零开始,现在就来看怎么快速构建一个ASP.NET MVC后台管理admin主页的方法,先看一看最终的效果! 第一步:选择一个合适的admin模板 互联网时代就是资源共享的时代,网上各种前端模板,这里主要是说明怎么把模板整合到我们的ASP.NET MVC项目中,至于模板大家可以自己去选择喜欢的,这里我们选择这个清爽版的AircraftAdmin,首先看看Aircr

BaseProject快速构建自己的APP

关于BaseProject BaseProject是一个快速构建app工程的开源项目,目的是为了更加方便的初始化一个工程,省去编写或者导入BaseActivity,BaseFragment,网络请求,工具类等基础又实用的代码.让你更加专注去实现自己产品需求,业务逻辑,而不是浪费时间在重复的工作上! https://github.com/flyzend/BaseProject 如何依赖BaseProject 1. 在project.gradle 文件中添加 maven { url "https:/

Yii2快速构建RESTful Web服务功能简介

Yii2相比Yii1而言,一个重大的改进是内置了功能完备的RESTful支持. 其内置RESTful支持提供了如下功能: 使用ActiveRecord的通用接口来快速构建原型: 应答格式协商(缺省支持 JSON 和 XML): 可定制的对象序列化,支持选择输出哪些列: 请求数据的格式化以及验证错误: 通过HTTP 动词映射实现高效路由: 支持 OPTIONS 和 HEAD 动词: 认证 和 鉴权: 支持 HATEOAS(RESTful的架构约束,超媒体即应用程序状态): 结果缓存,可使用 yii

快速构建App界面的框架(●&#39;?&#39;●) -----SalutJs

前言 卤煮在公司之初接触到的是一个微信APP应用.前端技术采用的是Backbone+zepto等小型JS类库.在项目开发之初,这类中小型的项目采用这两种库可以满足基本的需求.然而,随着迭代的更新和业务的增加,成堆的代码被覆盖到项目中去了,使得这样一种技术架构方式变得异常的臃肿,很多界面变得异常的难以维护,因此卤煮打算重构公司前端架构. 卤煮的想法是:采用异步模块的加载方式,将不同微信菜单进入的界面分成若干的模块文件,这样的好处是按照需求加载界面,而且每个界面都单独成模块,便于维护和独立开发.于是

Android 滑动导航菜单的快速构建(二) Material Design

原创文章,转载请注明 ( 来自:http://blog.csdn.net/leejizhou/article/details/52046748 李济洲的博客 ) 上一篇 http://blog.csdn.net/leejizhou/article/details/52013343 介绍了几个滑动导航菜单效果的快速构建,这篇文章来总结"当下"如何按照Android的设计标准去设计滑动导航菜单,我为什么说的"当下"呢?因为这个设计标准是会变的. 在material de

使用 JSONP 实现跨域通信,第 1 部分: 结合 JSONP 和 jQuery 快速构建强大的 mashup

http://www.ibm.com/developerworks/cn/web/wa-aj-jsonp1/ 简介 Asynchronous JavaScript and XML (Ajax) 是驱动新一代 Web 站点(流行术语为 Web 2.0 站点)的关键技术.Ajax 允许在不干扰 Web 应用程序的显示和行为的情况下在后台进行数据检索.使用 XMLHttpRequest 函数获取数据,它是一种 API,允许客户端 JavaScript 通过 HTTP 连接到远程服务器.Ajax 也是许

SylixOS在x86平台的快速构建

1.适用范围 本文档适用于使用RealEvo-IDE集成开发环境在x86平台快速构建SylixOS运行环境. 2.前提准备 在开发机上正确安装RealEvo-IDE集成开发环境. 一台正常的x86平台目标机(建议使用Intel处理器,包含显示器或者串口等输出设备和键盘等输入设备). 如果需要U盘安装则需要一个大于32M的空白U盘. 3.制作x86 bsp启动镜像 3.1准备base工程 3.1.1打开RealEvo-IDE 双击图标,打开RealEvo-IDE软件,如图 31所示选择建立工程的工

爬虫_快速构建实时抓取集群

定义: 首先,我们定义一下定向抓取,定向抓取是一种特定的抓取需求,目标站点是已知的,站点的页面是已知的.本文的介绍里面,主要是侧重于如何快速构建一个实时的抓取系统,并不包含通用意义上的比如链接分析,站点发现等等特性. 在本文提到的实例系统里面,主要用到linux+mysql+redis+django+scrapy+webkit,其中scrapy+webkit作为抓取端,redis作为链接库存储,mysql作为网页信息存储,django作为爬虫管理界面,快速实现分布式抓取系统的原型. 名词解析:

快速构建Windows 8风格应用22-MessageDialog

原文:快速构建Windows 8风格应用22-MessageDialog 本篇博文主要介绍MessageDialog概述.MessageDialog常用属性和方法.如何构建MessageDialog   MessageDialog概述 MessageDialog指的就是对话框. 对话框的命令栏中最多包含三个命令.如果我们指定任何命令,将会有一个默认命令添加到对话框中,目的是关闭对话框. 对话框弹出后界面中所有元素将在对话框下面显示,并且将会阻塞任何触摸事件直到用户进行响应对话框. 另外对话框应该