标签:failed   ict   password   tap   validate   info   resin   gui   sealed   
- 实现代码:MasterChief.DotNet.ProjectTemplate.WebApi
- Demo Code:https://github.com/YanZhiwei/MasterChief.ProjectTemplate.WebApiSample
- Nuget : Install-Package MasterChief.DotNet.ProjectTemplate.WebApi
- 实现WebApi开发中诸如授权验证,缓存,参数验证,异常处理等,方便快速构建项目而无需过多关心技术细节;
- 欢迎Star,欢迎PR;
目录
- 授权
- 鉴权
- 授权与鉴权使用
- 基于请求缓存处理
- 异常处理
- 参数验证
Created by gh-md-toc
授权
- 
授权接口,通过该接口自定义授权实现,项目默认实现基于Jwt授权 /// 
///     WebApi 授权接口
/// 
public interface IApiAuthorize
{
    /// 
    ///     检查请求签名合法性
    /// 
    /// 加密签名字符串
    /// 时间戳
    /// 随机数
    /// 应用接入配置信息
    /// CheckResult
    CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig);
    /// 
    ///     创建合法用户获取访问令牌接口数据
    /// 
    /// IdentityUser
    /// AppConfig
    /// IdentityToken
    ApiResult CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig);
}
 
- 
基于Jwt授权实现 /// 
///     基于Jwt 授权实现
/// 
public sealed class JwtApiAuthorize : IApiAuthorize
{
    /// 
    ///     检查请求签名合法性
    /// 
    /// 加密签名字符串
    /// 时间戳
    /// 随机数
    /// 应用接入配置信息
    /// CheckResult
    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();
    }
    /// 
    ///     创建合法用户获取访问令牌接口数据
    /// 
    /// IdentityUser
    /// AppConfig
    /// IdentityToken
    public ApiResult CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig)
    {
        ValidateOperator.Begin()
            .NotNull(identityUser, "IdentityUser")
            .NotNull(appConfig, "AppConfig");
        var payload = new Dictionary
        {
            {"iss", identityUser.UserId},
            {"iat", UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds}
        };
        var identityToken = new IdentityToken
        {
            AccessToken = CreateIdentityToken(appConfig.SharedKey, payload),
            ExpiresIn = appConfig.TokenExpiredDay * 24 * 3600
        };
        return ApiResult.Success(identityToken);
    }
    /// 
    ///     创建Token
    /// 
    /// 密钥
    /// 负载数据
    /// Token令牌
    public static string CreateIdentityToken(string secret, Dictionary 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鉴权 /// 
///     webApi 验证系统基本接口
/// 
public interface IApiAuthenticate
{
    #region Methods
    /// 
    ///     验证Token令牌是否合法
    /// 
    /// 令牌
    /// AppConfig
    /// CheckResult
    ApiResult CheckIdentityToken(string token, AppConfig appConfig);
    #endregion Methods
}
 
- 
基于Jwt鉴权实现 /// 
///     基于Jwt 授权验证实现
/// 
public sealed class JwtApiAuthenticate : IApiAuthenticate
{
    /// 
    ///     检查Token是否合法
    /// 
    /// 用户令牌
    /// AppConfig
    /// 
    public ApiResult 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.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.Fail($"用户ID{userid}令牌失效")
                : ApiResult.Success(userid);
        }
        catch (FormatException)
        {
            return ApiResult.Fail("用户令牌非法");
        }
        catch (SignatureVerificationException)
        {
            return ApiResult.Fail("用户令牌非法");
        }
    }
    /// 
    ///     转换Token
    /// 
    /// 令牌
    /// 密钥
    /// Token以及负载数据
    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构造函数方式,代码如下 /// 
///     Api授权
/// 
public abstract class AuthorizeController : ApiBaseController
{
    #region Constructors
    /// 
    ///     构造函数
    /// 
    /// IApiAuthorize
    /// IAppConfigService
    protected AuthorizeController(IApiAuthorize apiAuthorize, IAppConfigService appCfgService)
    {
        ValidateOperator.Begin()
            .NotNull(apiAuthorize, "IApiAuthorize")
            .NotNull(appCfgService, "IAppConfigService");
        ApiAuthorize = apiAuthorize;
        AppCfgService = appCfgService;
    }
    #endregion Constructors
    #region Fields
    /// 
    ///     授权接口
    /// 
    protected readonly IApiAuthorize ApiAuthorize;
    /// 
    ///     请求通道配置信息,可以从文件或者数据库获取
    /// 
    protected readonly IAppConfigService AppCfgService;
    #endregion Fields
    #region Methods
    /// 
    ///     创建合法用户的Token
    /// 
    /// 用户Id
    /// 用户密码
    /// 加密签名字符串
    /// 时间戳
    /// 随机数
    /// 应用接入ID
    /// OperatedResult
    protected virtual ApiResult 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.Fail(checkResult.Message);
        #endregion
        #region 用户鉴权
        var getIdentityUser = GetIdentityUser(userId, passWord);
        if (!getIdentityUser.State) return ApiResult.Fail(getIdentityUser.Message);
        #endregion
        #region 请求通道检查
        var getAppConfig = AppCfgService.Get(appid);
        if (!getAppConfig.State) return ApiResult.Fail(getAppConfig.Message);
        var appConfig = getAppConfig.Data;
        #endregion
        #region 检查请求签名检查
        var checkSignatureResult = ApiAuthorize.CheckRequestSignature(signature, timestamp, nonce, appConfig);
        if (!checkSignatureResult.State) return ApiResult.Fail(checkSignatureResult.Message);
        #endregion
        #region 生成基于Jwt Token
        var getTokenResult = ApiAuthorize.CreateIdentityToken(getIdentityUser.Data, getAppConfig.Data);
        if (!getTokenResult.State) return ApiResult.Fail(getTokenResult.Message);
        return ApiResult.Success(getTokenResult.Data);
        #endregion
    }
    /// 
    ///     检查用户的合法性
    /// 
    /// 用户Id
    /// 用户密码
    /// UserInfo
    protected abstract CheckResult 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形式,标注请求是否需要鉴权 /// 
 ///     WebApi 授权验证实现
 /// 
 [AttributeUsage(AttributeTargets.Method)]
 public abstract class AuthenticateAttribute : AuthorizationFilterAttribute
 {
     #region Constructors
     /// 
     ///     构造函数
     /// 
     /// IApiAuthenticate
     /// appCfgService
     protected AuthenticateAttribute(IApiAuthenticate apiAuthenticate, IAppConfigService appCfgService)
     {
         ValidateOperator.Begin()
             .NotNull(apiAuthenticate, "IApiAuthenticate")
             .NotNull(appCfgService, "IAppConfigService");
         ApiAuthenticate = apiAuthenticate;
         AppCfgService = appCfgService;
     }
     #endregion Constructors
     #region Fields
     /// 
     ///     授权验证接口
     /// 
     protected readonly IApiAuthenticate ApiAuthenticate;
     /// 
     ///     请求通道配置信息,可以从文件或者数据库获取
     /// 
     protected readonly IAppConfigService AppCfgService;
     #endregion Fields
     #region Methods
     /// 
     ///     验证Token令牌是否合法
     /// 
     /// 令牌
     /// 应用ID
     /// CheckResult
     protected virtual ApiResult CheckIdentityToken(string token, Guid appid)
     {
         #region 请求参数检查
         var checkResult = CheckRequest(token, appid);
         if (!checkResult.State)
             return ApiResult.Fail(checkResult.Message);
         #endregion
         #region 请求通道检查
         var getAppConfig = AppCfgService.Get(appid);
         if (!getAppConfig.State) return ApiResult.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,可以自定义构建参数处理方式 /// 
/// 请求参数
/// 
public sealed class ValidateRequestAttribute : ValidateModelAttribute
{
    public override void OnParameterIsNulling(HttpActionContext actionContext)
    {
        actionContext.Response =
            actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult.Fail("请求参数非法。"));
    }
    public override void OnParameterInvaliding(HttpActionContext actionContext, ValidationFailedResult result)
    {
        var message = result.Data.FirstOrDefault()?.Message;
        actionContext.Response =
            actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult.Fail(message));
    }
}
 
[开源]WebApi 快速构建示例
标签:failed   ict   password   tap   validate   info   resin   gui   sealed   
原文地址:https://www.cnblogs.com/MeetYan/p/WebApi.html