标签: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