首页 技术 正文
技术 2022年11月12日
0 收藏 653 点赞 4,604 浏览 11953 个字
  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));
    }
    }
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,491
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,907
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,740
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,492
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,132
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,293