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

目录

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));
    }
    }

[开源]快速构建一个WebApi项目的更多相关文章

  1. springboot:快速构建一个springboot项目

    前言: springboot作为springcloud的基础,springboot的热度一直很高,所以就有了这个springboot系列,花些时间来了解和学习为自己做技术储备,以备不时之需[手动滑稽] ...

  2. 快速构建一个vue项目

    首先介绍一下命令行构建一个vue项目步骤: 1.下载安装node.js(直接运行安装包根据步骤安装完),打开命令行输入:node -v ,出现版本号即安装成功. 2.命令行界面输入:cnpm inst ...

  3. 快速构建一个springboot项目(一)

     前言: springcloud是新一代的微服务框架而springboot作为springcloud的基础,很有必要对springboot深入学习一下. springboot能做什么? (1)spri ...

  4. 【springBoot】之快速构建一个web项目

    基于maven,首先看pom文件 <parent> <groupId>org.springframework.boot</groupId> <artifact ...

  5. 快速构建一个简单的单页vue应用

    技术栈 vue-cli webpack vux,vux-loader less,less-loader vue-jsonp vue-scroller ES6 vue-cli:一个vue脚手架工具,利用 ...

  6. 利用 vue-cli 构建一个 Vue 项目

    一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...

  7. Eclipse的maven构建一个web项目,以构建SpringMVC项目为例

    http://www.cnblogs.com/javaTest/archive/2012/04/28/2589574.html springmvc demo实例教程源代码下载:http://zuida ...

  8. 【jQuery插件】用jQuery Masonry快速构建一个pinterest网站布局(转)

    [jQuery插件]用jQuery Masonry快速构建一个pinterest网站布局 时间:2011年03月21日作者:愚人码头查看次数:29,744 views评论次数:25条评论 前段时间领导 ...

  9. jenkins构建一个maven项目[五]

    标签(linux): jenkins 笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 构建一个maven项目,即为构建java项目.模拟实验之前先把实验代码推送到 ...

随机推荐

  1. Unity Rain Ai 插件基本使用(二)

    前言 在前面的教程中我们已经基本实现了路径导航和障碍物规避. 但是这样我们并没有让我们的角色学会思考,他只是机械的去完成一些步骤,这并不能体现Rain插件的智能. 一个角色他应该有多个不同的状态,待机 ...

  2. PBRT笔记(6)——采样和重构

    前言 本文仅作为个人笔记分享,又因为本章涉及多个专业领域而本人皆未接触过,所以难免出错,请各位读者注意. 对于数字图像需要区分image pixels(特定采样处的函数值)和display pixel ...

  3. 平时作业七 Java

    以下是几本计算机书籍的基本信息编号 书名 价格 出版社1 JAVA基础 32 清华大学出版社2 JAVA WEB开发 40 电子工业出版社3 面向对象程序设计 28 清华大学出版社4 Struts开发 ...

  4. tensorflow 使用 3 模型学习

    模型学习 import tensorflow as tf import numpy as np # 生成 100 个随机的点 x_data = np.random.rand( 100 ) y_data ...

  5. [CF364D]Ghd

    [CF364D]Ghd 题目大意: 有\(n(n\le10^6)\)个数\(A_{1\sim n}(A_i\le10^{12})\),从中选取\(\lceil\frac n2\rceil\)个数,使得 ...

  6. 对象关系映射 ORM

    1.1 作用 MTV框架中包括一个重要的部分,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因 ...

  7. js计算发表的时间...分钟/小时以前/以后

    网上找的都好复杂,这本来就是个粗略显示通俗的时间,绕来绕去都晕了 function timeAgo(o){ var n=new Date().getTime(); var f=n-o; var bs= ...

  8. 对Jpa中Entity关系映射中mappedBy的理解

    mappedBy 单向关系不需要设置该属性,双向关系必须设置,避免双方都建立外键字段数据库中1对多的关系,关联关系总是被多方维护的即外键建在多方,我们在单方对象的@OneToMany(mappedby ...

  9. vue+element-ui之tree树形控件有关子节点和父节点之间的各种选中关系详解

    做后端管理系统,永远是最蛋疼.最复杂也最欠揍的事情,也永远是前端开发人员最苦逼.最无奈也最尿性的时刻.蛋疼的是需求变幻无穷,如同二师兄的三十六般变化:复杂的是开发难度寸步难行,如同蜀道难,难于上青天: ...

  10. node01

    ---恢复内容开始--- 1.node初体验 安装完成node,写好相应的js代码后,在cmd中node 文件名即可完成编译执行过程. 2.尝试使用node搭建一个简单服务器 //引入http模块 c ...