[开源]快速构建一个WebApi项目
- 项目代码:MasterChief.DotNet.ProjectTemplate.WebApi
- 示例代码:https://github.com/YanZhiwei/MasterChief.ProjectTemplate.WebApiSample
- Nuget : Install-Package MasterChief.DotNet.ProjectTemplate.WebApi
- 实现WebApi开发中诸如授权验证,缓存,参数验证,异常处理等,方便快速构建项目而无需过多关心技术细节;
- 欢迎Star,欢迎Issues;
目录
Created by gh-md-toc
授权
授权接口,通过该接口自定义授权实现,项目默认实现基于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);
}
基于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);
}
}
鉴权
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
}
基于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);
}
}
授权与鉴权使用
授权使用,通过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
}
鉴权使用,通过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
}
基于请求缓存处理
通过ICacheProvider接口,可以扩展缓存数据方式;
通过配置DependsOnIdentity参数,可以配置是否依赖Token令牌进行缓存;
通过配置CacheMinutes参数,可以指定具体接口缓存时间,当设置0的时候不启用缓存;
通过实现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);
}
}
异常处理
通过实现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;
}
}
参数验证
通过实现ValidateModelAttribute,以及DataAnnotations快速构建请求参数验证
请求参数只需要DataAnnotations标注即可;
public sealed class ArticleRequest
{
[Required(ErrorMessage = "缺少文章ID")]
public int Id
{
get;
set;
} }
项目实现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项目的更多相关文章
- springboot:快速构建一个springboot项目
前言: springboot作为springcloud的基础,springboot的热度一直很高,所以就有了这个springboot系列,花些时间来了解和学习为自己做技术储备,以备不时之需[手动滑稽] ...
- 快速构建一个vue项目
首先介绍一下命令行构建一个vue项目步骤: 1.下载安装node.js(直接运行安装包根据步骤安装完),打开命令行输入:node -v ,出现版本号即安装成功. 2.命令行界面输入:cnpm inst ...
- 快速构建一个springboot项目(一)
前言: springcloud是新一代的微服务框架而springboot作为springcloud的基础,很有必要对springboot深入学习一下. springboot能做什么? (1)spri ...
- 【springBoot】之快速构建一个web项目
基于maven,首先看pom文件 <parent> <groupId>org.springframework.boot</groupId> <artifact ...
- 快速构建一个简单的单页vue应用
技术栈 vue-cli webpack vux,vux-loader less,less-loader vue-jsonp vue-scroller ES6 vue-cli:一个vue脚手架工具,利用 ...
- 利用 vue-cli 构建一个 Vue 项目
一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...
- Eclipse的maven构建一个web项目,以构建SpringMVC项目为例
http://www.cnblogs.com/javaTest/archive/2012/04/28/2589574.html springmvc demo实例教程源代码下载:http://zuida ...
- 【jQuery插件】用jQuery Masonry快速构建一个pinterest网站布局(转)
[jQuery插件]用jQuery Masonry快速构建一个pinterest网站布局 时间:2011年03月21日作者:愚人码头查看次数:29,744 views评论次数:25条评论 前段时间领导 ...
- jenkins构建一个maven项目[五]
标签(linux): jenkins 笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 构建一个maven项目,即为构建java项目.模拟实验之前先把实验代码推送到 ...
随机推荐
- 在 Vim 中优雅地查找和替换(转)
总有人问我 Vim 中能不能查找,当然能!而且是超级强的查找! 这篇文章来详细介绍 Vim 中查找相关的设置和使用方法. 包括查找与替换.查找光标所在词.高亮前景/背景色.切换高亮状态.大小写敏感查找 ...
- php获取当前时间的毫秒数
floor(microtime()*1000); 用microtime能输出当前的秒的后面8位小数 乘以1000取整数就行了
- JavaScript递归
什么是递归? 在函数的内部调用自己 下面有一个例子,通过这个例子,大家就可以了解什么是递归 function fun(){ console.log(new Date()) //获取当前时间,并在控制台 ...
- 旧项目Makefile 迁移CMake的一种方法:include Makefile
有些c++旧项目用Makefile,要迁移CMake的时候非常痛苦,有些像static pattern的语法和make自带命令 cmake要重写一套非常的麻烦. 因此这里用trick的方法实现了一种i ...
- grafana--邮箱告警配置
安装 wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-6.0.2-1.x86_64.rpm yum l ...
- vuex学习
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 简单的理解就是你在state中定义了一个数 ...
- (BUG记录)使用迭代器安全的删除处于循环下集合中的元素
今日在写一个功能时,需要从MQ拿取数据集合调用对端系统进行批量处理,为了幂等支持,在循环内部如果不满足调用条件就直接从集合中移除. 以上是一个典型的循环集合内删除的场景任务,工作一年第一次遇到这个场景 ...
- 推荐自学JAVA开发的三本书
---------------------------------------------------------------------------------------------------- ...
- HBuilder git使用-建立仓库,邀请用户
1.git环境配置好后,在Github上注册好帐号 2. 创建一个Respository(代码仓库) 3.邀请其他小组用户(必须的,要不别人提交不了修改) 4.把邀请链接要COPY给其他用户 5. 其 ...
- Shell语言
1.shell脚本规范以.sh结尾 2.运行 3.赋予权限,查询shell的执行过程 输出时间的 输出日历 输出一年的日历 修改语言 计算机 read –t 3 –p “1111111111” # ...