zoukankan      html  css  js  c++  java
  • asp.net core利用DI实现自定义用户系统,脱离ControllerBase.User

    前言

    很多时候其实我们并不需要asp.net core自带的那么复杂的用户系统,基于角色,各种概念,还得用EF Core,而且在web应用中都是把信息存储到cookie中进行通讯(我不喜欢放cookie中,因为有次我在mac系统中的safari浏览器运行web应用时,碰到跨域cookie设不上,非要使用个很特殊的方法,记得是iframe,挺麻烦的,所以我还是喜欢放自定义header中), 用了以后感觉被微软给绑架了。不过这完全是个人喜好,大家完全可以按自己喜欢的来,我这里提供了另外一条路,大家可以多一种选择。

    我这边是利用asp.net core的依赖注入,定义了一套属于自己系统的用户认证与授权,大家可以参考我这个来定义自己的,也不局限于用户系统。

    面向切面编程(AOP)

    在我看来,Middleware与Filter都是asp.net core中的切面,我们可以把认证与授权放到这两块地方。我个人比较喜欢把认证放到Middleware,可以提早把那些不合法的攻击拦截返回。

    依赖注入(DI)

    依赖注入有3种生命周期

    1. 在同一个请求发起到结束。(services.AddScoped)

    2. 每次注入的时候都是新建。(services.AddTransient)

    3. 单例,应用开始到应用结束。(services.AddSingleton)

    我的自定义用户类采用的是services.AddScoped。

    具体做法

    1. 定义用户类

    1     // 用户类,随便写的
    2     public class MyUser
    3     {
    4         public string Token { get; set; }
    5         public string UserName { get; set; }
    6     }

    2. 注册用户类

    Startup.cs中的ConfigureServices函数:

    1         // This method gets called by the runtime. Use this method to add services to the container.
    2         public void ConfigureServices(IServiceCollection services)
    3         {
    4             ...
    5             // 注册自定义用户类
    6             services.AddScoped(typeof(MyUser));
    7             ...
    8         }

    自定义用户类,是通过services.AddScoped方式进行注册的,因为我希望它在同一个请求中,Middleware, filter, controller引用到的是同一个对象。

    3. 注入到Middleware

     1     // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
     2     public class AuthenticationMiddleware
     3     {
     4         private readonly RequestDelegate _next;
     5         private IOptions<HeaderConfig> _optionsAccessor;
     6 
     7         public AuthenticationMiddleware(RequestDelegate next, IOptions<HeaderConfig> optionsAccessor)
     8         {
     9             _next = next;
    10             _optionsAccessor = optionsAccessor;
    11         }
    12 
    13         public async Task Invoke(HttpContext httpContext, MyUser user)
    14         {
    15             var token = httpContext.Request.Headers[_optionsAccessor.Value.AuthHeader].FirstOrDefault();
    16             if (!IsValidate(token))
    17             {
    18                 httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
    19                 httpContext.Response.ContentType = "text/plain";
    20                 await httpContext.Response.WriteAsync("UnAuthentication");
    21             }
    22             else
    23             {
    24                 // 设置用户的token
    25                 user.Token = token;
    26                 await _next(httpContext);
    27             }
    28         }
    29 
    30         // 随便写的,大家可以加入些加密,解密的来判断合法性,大家自由发挥
    31         private bool IsValidate(string token)
    32         {
    33             return !string.IsNullOrEmpty(token);
    34         }
    35     }
    36 
    37     // Extension method used to add the middleware to the HTTP request pipeline.
    38     public static class AuthenticationMiddlewareExtensions
    39     {
    40         public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder)
    41         {
    42             return builder.UseMiddleware<AuthenticationMiddleware>();
    43         }
    44     }

    我发现如果要把接口/类以Scoped方式注入到Middleware中,就需要把要注入的类/接口放到Invoke函数的参数中,而不是Middleware的构造函数中,我猜这也是为什么Middleware没有继承基类或者接口,在基类或者接口中定义好Invoke的原因,如果它在基类或者接口中定义好Invoke,势必这个Invoke的参数要固定死,就不好依赖注入了。

    4. 配置某些路径才会使用该Middleware

     1         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
     2         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
     3         {
     4             loggerFactory.AddConsole(Configuration.GetSection("Logging"));
     5             loggerFactory.AddDebug();
     6             // Set up nlog
     7             loggerFactory.AddNLog();
     8             app.AddNLogWeb();
     9 
    10             // 除了特殊路径外,都需要加上认证的Middleware
    11             app.MapWhen(context => !context.Request.Path.StartsWithSegments("/api/token")
    12                                  && !context.Request.Path.StartsWithSegments("/swagger"), x =>
    13             {
    14                 // 使用自定义的Middleware
    15                 x.UseAuthenticationMiddleware();
    16                 // 使用通用的Middleware
    17                 ConfigCommonMiddleware(x);
    18             });
    19             // 使用通用的Middleware
    20             ConfigCommonMiddleware(app);
    21 
    22             // Enable middleware to serve generated Swagger as a JSON endpoint.
    23             app.UseSwagger();
    24 
    25             // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
    26             app.UseSwaggerUI(c =>
    27             {
    28                 c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    29             });
    30         }
    31 
    32         // 配置通用的Middleware
    33         private void ConfigCommonMiddleware(IApplicationBuilder app)
    34         {
    35             // cors
    36             app.UseCors("AllowAll");
    37 
    38             app.UseExceptionMiddleware();
    39             // app.UseLogRequestMiddleware();
    40             app.UseMvc();
    41         }

    像获取token啊,查看api文档啊就不需要认证了。

    5. 注入到Filter

     1     public class NeedAuthAttribute : ActionFilterAttribute
     2     {
     3         private string _name = string.Empty;
     4         private MyUser _user;
     5 
     6         public NeedAuthAttribute(MyUser user, string name = "")
     7         {
     8             _name = name;
     9             _user = user;
    10         }
    11 
    12         public override void OnActionExecuting(ActionExecutingContext context)
    13         {
    14             this._user.UserName = "aaa";
    15         }
    16     }

    这里我创建的是个带字符串参数的类,因为考虑到这个Filter有可能会被复用,比如限制某个接口只能被某种用户访问, 这个字符串便可以存某种用户的标识。

    Filter中还可以注入数据库访问的类,这样我们便可以到数据库中通过token来获取到相应的用户信息。

    6. 使用Filter

    1 [TypeFilter(typeof(NeedAuthAttribute), Arguments = new object[]{ "bbb" }, Order = 1)]
    2 public class ValuesController : Controller

    这里使用了TypeFilter,以加载使用了依赖注入的Filter, 并可以设置参数,跟Filter的顺序。

    默认Filter的顺序是 全局设置->Controller->Action, Order默认都为0,我们可以通过设置Order来改变这个顺序。

    7. 注入到Controller

     1     public class ValuesController : Controller
     2     {
     3         private MyUser _user;
     4 
     5         public ValuesController(MyUser user)
     6         {
     7             _user = user;
     8         }
     9         ...
    10     }

    注入到Controller的构造函数中,这样我们就可以在Controller的Action中使用我们自定义的用户,就能知道到底当前是哪个用户在调用这个Action。

  • 相关阅读:
    正则表达式
    request库解析
    urllib库解析
    爬虫入门基本原理
    图的遍历dfs和bfs
    KMP算法
    Linux操作系统实验-线程同步
    Leetcode 183场周赛
    并查集--Disjoint Set
    C#杂乱知识汇总
  • 原文地址:https://www.cnblogs.com/nickppa/p/6903694.html
Copyright © 2011-2022 走看看