• [开源]快速构建一个WebApi项目


    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));
          }
      }
      
  • 相关阅读:
    图像识别试验
    uCos-III移植到STM32F10x
    我为什么要学习C++反汇编
    网络爬虫基本原理(一)
    JavaScript对象模型-执行模型
    gdb core调试
    进程、轻量级进程(LWP)、线程
    谁动了我的cpu——oprofile使用札记
    Linux IO多路复用之epoll网络编程(含源码)
    黑客常用WinAPI函数整理
  • 原文地址:https://www.cnblogs.com/MeetYan/p/WebApi.html
Copyright © 2020-2023  润新知