从壹开始【NetCore3.0】 46 ║ 授权认证:自定义返回格式

正文

前言

哈喽大家好,马上就要年末了,距离新的一年,只有50天了,春节是75天。

13652172-33e3823be1956a5c.png
image

在这个时节内,天气逐渐变凉,但是大家的心肯定很热吧,因为发生了两件大事:

1、双十一买买买,在这个让人激动又纠结的一天,大家有没有被像 “高考命题组” 般的优惠方案搞得云里来雾里去?最终,我选择了 <不买东西优惠100%> 的最优解方案,其实是 Q I O N G 第二声😂。

2、还有一个特别轰动的,当属前两天上海举办的 "中国.NET开发者峰会,可以这样读NET Conf(c,o,n ,f)",真的特别轰动,各路大神齐聚,只可惜我当时正在开启微讲堂,为了履行我上次公众号点赞的诺言——10小时入门net core的远程视频授课🤣。

言归正传,曾几何时,在某微信群讨论 Http 状态码的时候,被某大佬给怼了一下,具体的内容就不说了,反正现在的返回状态码无非就那两个方案,一个是用 RESTFul 风格,完全通过 http状态码来处理,另一个就是通过 自定义返回内容,比如json的格式,把状态信息放到返回内容里边,最终我没有听从他的意见,还是坚持我自己的风格(状态码+自定义格式),具体的内容我都会在下面详细的说明的,恰逢QQ群里有一个小伙伴也说到了关于封装状态码的问题,其实我已经写了,只不过他的更优雅,更漂亮,所以我就用他的方案了:

投稿人:QQ群:菜工 、 飞非→飛

主题:封装授权认证的自定义返回格式。

代码:Blog.Core 主分支

具体内容:详见下文。

13652172-fda37b7ccabc5060.png
image

一、两种返回格式的思考

在上边的文章中呢,我和某大佬基于返回格式简单的表明了下个人的立场,其实我自己也懂,无非就那么两个情况:

1、完全基于 HTTP 返回格式状态码

说这个可能有点儿抽象,我举个例子大家就懂了:

[
13652172-35095a6810c6cdab.gif
复制代码

](javascript:void(0); "复制代码")

<pre>namespace Microsoft.AspNetCore.Http
{ public static class StatusCodes
{ public const int Status100Continue = 100; public const int Status101SwitchingProtocols = 101; public const int Status102Processing = 102; public const int Status200OK = 200; // 等等等等

    public const int Status400BadRequest = 400; public const int Status401Unauthorized = 401; public const int Status402PaymentRequired = 402; public const int Status403Forbidden = 403; public const int Status404NotFound = 404; public const int Status405MethodNotAllowed = 405; public const int Status406NotAcceptable = 406; public const int Status414RequestUriTooLong = 414; public const int Status414UriTooLong = 414; public const int Status415UnsupportedMediaType = 415; public const int Status416RangeNotSatisfiable = 416; public const int Status416RequestedRangeNotSatisfiable = 416; public const int Status417ExpectationFailed = 417; public const int Status418ImATeapot = 418; public const int Status419AuthenticationTimeout = 419; public const int Status421MisdirectedRequest = 421; public const int Status422UnprocessableEntity = 422; public const int Status423Locked = 423; public const int Status424FailedDependency = 424; // 等等等等

    public const int Status500InternalServerError = 500; public const int Status501NotImplemented = 501; public const int Status502BadGateway = 502; public const int Status503ServiceUnavailable = 503; public const int Status504GatewayTimeout = 504; public const int Status505HttpVersionNotsupported = 505; public const int Status506VariantAlsoNegotiates = 506; public const int Status507InsufficientStorage = 507; public const int Status508LoopDetected = 508; public const int Status510NotExtended = 510; public const int Status511NetworkAuthenticationRequired = 511;
}

}</pre>

[
13652172-a42222cb640a6112.gif
复制代码

](javascript:void(0); "复制代码")

13652172-7b16030d5a016eba.png
image

上边的就是官方给定的 Http 状态码,我删了一些,大家可以看出来,官方给的特别多,也特别的全,已经能满足我们平时开发的所有需要,完全没问题,而且呢,这样还有一个好处,就是比如前端的项目,比如 VUE ,可以根据 http 状态码来进行拦截器进行封装,而不用看返回结果了,单单从 statuscode 上,就直接统一拦截,这样看似特别完美,那为啥还会有第二种解决方案呢,请继续往下看。

2、自定义返回格式内容

上边的方法真的就特别完美么,首先,拦截器这个优点,并不是只能用在拦截 http statuscode 上,针对具体的返回内容也可以拦截。

其次,大家可能偶尔会遇到过这个情况,就是访问微信或者什么的时候,会出现提示 “5003 xxxxxx异常”,大家可以看一下,这个返回状态码,http 是没有的。

而且,websocket 也并没有那些所谓的 404 、503吧,这个时候就需要我们去自定义,比如这样的:

13652172-a3451c252bd16869.png
image

这就是第二种解决方案,这两种方案其实一直都存在我们的平时开发过程中的,当然我是都在用的,我目前自己的开源项目里,用的是第一种解决方案,偶尔也会有第二种,公司的某些项目里,用的是第二种,因为有时候状态信息太多,必须去自定义,所以这两种方案我都是支持的,也不用说这个不对,那个错误,而且我也同时用了这两个

那既然两种都支持,如果两个我都想用,怎么封装一下呢,没问题,我就在 Blog.Core 项目里,对 授权认证 返回格式封装一下,大家看看吧,原理我以后会在直播里讲,这里就不细说了,直接讲操作步骤。

二、自定义授权认证返回格式

1、复杂的策略授权

那既然说到了返回格式,肯定得有一个场景,那我就用我的复杂策略授权 PermissionHandler.cs 来举例子,大家平时也都用过,我在本周三的直播中,会详细说名这个复杂策略授权的运行机制,到时候也会有录屏,大家到时候看看就知道了,这里不细说。

简单来说,就是获取当前 token 的角色信息和访问的URL地址,做匹配和判断,判断是否有权限,有,就 succeed,没有就 failed(这里可能是 401 ,也可能是403)。

当没有登录的时候,就是 没有登录,或者token过期的时候,我们就 failed,会自动返回 401;

当token还有效,但是不匹配Role 和 URL 的时候,我们返回 failed,会自动返回 403 状态码;

这里截图部分代码,注意下,这里如果你之前写其他返回内容了,要删掉,只保留 failed 和 return:

13652172-553835ca6a57bfe6.png
image

但是,虽然是返回 401 和 403了,他们是这样的,这种不好看,而且也没有具体的响应 Message,不太友好

13652172-5a01ed7cc6fa28b5.png
image

所以我们就需要自定义返回内容的格式。

2、定义响应实体类

我这里写了一个很 low 的类,具体就是那个意思,大家看看即可,有更优雅的可以帮忙说说,或者提交个 PR:

[
13652172-1a8fd186f615280e.gif
复制代码

](javascript:void(0); "复制代码")

<pre>namespace Blog.Core.AuthHelper.Policys
{ public class ApiResponse
{ public int Status { get; set; } = 404; public object Value { get; set; } = "No Found"; public ApiResponse(StatusCode apiCode)
{ switch (apiCode)
{ case StatusCode.CODE401:
{
Status = 401;
Value = "很抱歉,您无权访问该接口,请确保已经登录!";
} break; case StatusCode.CODE403:
{
Status = 403;
Value = "很抱歉,您的访问权限等级不够,联系管理员!";
} break;
}
}
} public enum StatusCode
{
CODE401,
CODE403,
CODE404,
CODE500
}

}</pre>

[
13652172-6a162c7d0c6aa2f8.gif
复制代码

](javascript:void(0); "复制代码")

这个实体类,是用来返回响应内容的,如何使用,请往下看。

3、定义响应处理器

那我们既然自定义了响应内容,就需要定义响应处理器,方法就是继承抽象类 AuthenticationHandler<TOptions> ,然后重写方法:

[
13652172-c7abe1b4fe6fb1e8.gif
复制代码

](javascript:void(0); "复制代码")

<pre>namespace Blog.Core.AuthHelper
{ public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions> { public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
} protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{ throw new NotImplementedException();
} protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.ContentType = "application/json";
Response.StatusCode = StatusCodes.Status401Unauthorized; await Response.WriteAsync(JsonConvert.SerializeObject(new ApiResponse(StatusCode.CODE401)));
} protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
Response.ContentType = "application/json";
Response.StatusCode = StatusCodes.Status403Forbidden; await Response.WriteAsync(JsonConvert.SerializeObject(new ApiResponse(StatusCode.CODE403)));
}

}

}</pre>

[
13652172-95ec1737e67cd838.gif
复制代码

](javascript:void(0); "复制代码")

这里很简单,只是重写了两个异步方法,然后将内容 Response 出去即可。

4、替换默认Scheme方案

在上边我们说到了,我们的认证服务 services.AddAuthentication() 它自己有一套返回格式和内容,就是上边截图的内容,那我们要修改,就需要给替换掉:

13652172-33401bb2d9d4cf44.png
image

[
13652172-31ee1ebae56e936e.gif
复制代码

](javascript:void(0); "复制代码")

<pre> // 开启Bearer认证
services.AddAuthentication(o=> {
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = nameof(ApiResponseHandler);
o.DefaultForbidScheme = nameof(ApiResponseHandler);
}) // 添加JwtBearer服务
.AddJwtBearer(o => {
o.TokenValidationParameters = tokenValidationParameters;
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context => { // 如果过期,则把<是否过期>添加到,返回头信息中
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
} return Task.CompletedTask;
}
};
})
.AddScheme<AuthenticationSchemeOptions, ApiResponseHandler>(nameof(ApiResponseHandler), o => { });</pre>

[
13652172-582eb6db78c009f3.gif
复制代码

](javascript:void(0); "复制代码")

这个大家要注意一下,我已经把 Starup 中的服务都提取出来了,一共十个,这样大家在学习的时候,更方便。

到目前为止,我们就已经修改完成了,我们可以看看效果:

13652172-dab7e5be746a4031.png
image

不仅使用了 HTTP 的 StatusCode 状态码,同时也可以自定义返回内容,两个方案都兼容了,具体自己项目如何去使用,就看自己的需求了。

三、预告

这两天重新开始写 IdentityServer4 了,打算将我们的项目统一整合到 Ids 的授权服务中心里,同时也会录一个视频教程,因为 Blog.Core 的视频教程已经完结,下一个是 Blog.IdentityServer 的视频教程。

如果你有什么问题,或者疑问,或者想了解的,请留言评论。

四、Github && Gitee

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

一起学习,一起进步 QQ群:867095512

13652172-dcaffe37e30d89e5.png
关注我哦
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值