• 循序渐进学.Net Core Web Api开发系列【15】:应用安全


    原文:循序渐进学.Net Core Web Api开发系列【15】:应用安全

    系列目录

    循序渐进学.Net Core Web Api开发系列目录

     本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi

    一、概述

    本篇介绍Web系统的应用安全,主要涉及用户的身份认证和访问权限问题。

    大部分web应用习惯采用Session来保存用户认证信息,对于WebApi而言,调用者不一定是Web浏览器,可能是Android、iOS客户端,可能是微信小程序,也可能是客户端程序等等,这些客户端模拟构造cookie、存储或传递sessionid都不是太方便,这种情况下,采用令牌(tockenid)的方式进行授权管理就显得比较方便,唯一不方便的就是每次调用都要传递tockenid。

    基本流程如下:

    1、调用登陆接口,通过正确的用户名和密码活动TockenID;

    2、通过TockenID调用其他业务接口。

    二、基本使用

    1、处理用户登陆的Controller

    复制代码
            [HttpPost("login")]
            public ResultObject Login(string loginname,string password)
            {
                try
                {
                    User user = _context.Users
                        .AsNoTracking()
                        .Where(a => a.LoginName == loginname && a.Password == password)
                        .Single();
    
                String tockenid </span>=<span style="color: #000000;"> Tocken.GetTockenID();
    
                _cache.Set(tockenid, user, </span><span style="color: #0000ff;">new</span><span style="color: #000000;"> MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(</span><span style="color: #800080;">20</span><span style="color: #000000;">)));
    
                </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> ResultObject
                {
                    state </span>=<span style="color: #000000;"> ResultState.Success,
                    result </span>=<span style="color: #000000;"> tockenid
                };
            }
            </span><span style="color: #0000ff;">catch</span><span style="color: #000000;">(InvalidOperationException ex)
            {
                </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> ResultObject
                {
                    state </span>=<span style="color: #000000;"> ResultState.Exception,
                    ExceptionString </span>= <span style="color: #800000;">"</span><span style="color: #800000;">未找到匹配的数据</span><span style="color: #800000;">"</span><span style="color: #000000;">
                };
            }           
        }</span></pre>
    
    复制代码

    首先在数据库寻找匹配的用户信息,如果验证成功就以TockenID为主键把用户信息存入缓存,并设置过期时间(示例代码中过期时间为20秒),然后返回TockenID。

    2、在业务Controller中根据传入TockenID的进行用户认证。

    复制代码
    [HttpGet]
            public ResultObject GetAllArticles(string tockenid)
            {
                User user = null;
               if(!_cache.TryGetValue(tockenid,out user))
                {
                    return new ResultObject
                    {
                        state = ResultState.Fail,
                        ExceptionString = "请登陆"
                    };
                }
    
            List</span>&lt;Article&gt; articles =<span style="color: #000000;"> _context.Articles
                    .AsNoTracking()
                    .ToList</span>&lt;Article&gt;<span style="color: #000000;">();
    
            </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> ResultObject
            {
                state </span>=<span style="color: #000000;"> ResultState.Success,
                result </span>=<span style="color: #000000;"> articles
            };
        }</span></pre>
    
    复制代码

    三、采用中间件进行用户认证

    因为每个业务Controller都需要进行认证,所以按上述方法就比较麻烦了,我们做个中间件来进行统一身份验证

    复制代码
    namespace SaleService.System.Middleware
    {
        public class UserAuthenticationMiddleware
        {
            private readonly RequestDelegate _next;
            private readonly ILogger _logger;
            private readonly IMemoryCache _cache;
    
        </span><span style="color: #0000ff;">public</span> UserAuthenticationMiddleware(RequestDelegate next, ILogger&lt;UserAuthenticationMiddleware&gt;<span style="color: #000000;"> logger, IMemoryCache memoryCache)
        {
            _next </span>=<span style="color: #000000;"> next;
            _logger </span>=<span style="color: #000000;"> logger;
            _cache </span>=<span style="color: #000000;"> memoryCache;
        }
    
        </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task Invoke(HttpContext context)
        { <br></span><span style="color: #008000;">            //</span><span style="color: #008000;">如果是登陆接口就不需要验证Tocken</span>
            <span style="color: #0000ff;">if</span> (context.Request.Path.ToString().ToLower().StartsWith(<span style="color: #800000;">"</span><span style="color: #800000;">/api/user/login</span><span style="color: #800000;">"</span><span style="color: #000000;">))
            {
                </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> _next(context);
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
            }
    
            </span><span style="color: #0000ff;">if</span> (context.Request.Path.ToString().ToLower().StartsWith(<span style="color: #800000;">"</span><span style="color: #800000;">/api/</span><span style="color: #800000;">"</span><span style="color: #000000;">))
            {
                </span><span style="color: #0000ff;">string</span> tockenid = context.Request.Query[<span style="color: #800000;">"</span><span style="color: #800000;">tockenid</span><span style="color: #800000;">"</span><span style="color: #000000;">];
    
                </span><span style="color: #0000ff;">if</span> (tockenid == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
                {  
                    </span><span style="color: #0000ff;">var</span> result = <span style="color: #0000ff;">new</span><span style="color: #000000;"> ResultObject
                    {
                        state </span>=<span style="color: #000000;"> ResultState.Exception,
                        ExceptionString </span>= <span style="color: #800000;">"</span><span style="color: #800000;">Need tockenid</span><span style="color: #800000;">"</span><span style="color: #000000;">
                    };
    
                    context.Response.ContentType </span>= <span style="color: #800000;">"</span><span style="color: #800000;">application/json; charset=utf-8</span><span style="color: #800000;">"</span><span style="color: #000000;">;
                    context.Response.WriteAsync(JsonConvert.SerializeObject(result));
                    </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
                }               
    
                User user </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
                </span><span style="color: #0000ff;">if</span> (!_cache.TryGetValue(tockenid, <span style="color: #0000ff;">out</span><span style="color: #000000;"> user))
                {    
                    context.Response.StatusCode </span>= <span style="color: #800080;">401</span><span style="color: #000000;">;
                    context.Response.ContentType </span>= <span style="color: #800000;">"</span><span style="color: #800000;">application/json; charset=utf-8</span><span style="color: #800000;">"</span><span style="color: #000000;">;
                    context.Response.WriteAsync(</span><span style="color: #800000;">"</span><span style="color: #800000;">Invalidate tockenid(用户认证失败)</span><span style="color: #800000;">"</span><span style="color: #000000;">);
                    </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
                }
            }
            </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> _next(context);
        }
    }
    
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> UserAuthenticationMiddlewareExtensions
    {
        </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> IApplicationBuilder UseUserAuthentication(<span style="color: #0000ff;">this</span><span style="color: #000000;"> IApplicationBuilder builder)
        {
            </span><span style="color: #0000ff;">return</span> builder.UseMiddleware&lt;UserAuthenticationMiddleware&gt;<span style="color: #000000;">();
        }
    }
    

    }

    复制代码

    该中间件直接截取Request中的tockid进行验证,如果验证不通过就直接返回“短路”其他中间件,所以在使用时需要放在MVC中间件前面。

    复制代码
    public class Startup
        {
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials()); app.UseStaticFiles(); app.UseUserAuthentication();//要放在UseMvc前面 app.UseMvcWithDefaultRoute(); } }
    复制代码

     此时业务Controller就比较简单干净了,专心做业务就可以了

    复制代码
          [HttpGet]
            public ResultObject GetAllArticles(string tockenid)
            {    
                List<Article> articles = _context.Articles
                        .AsNoTracking()
                        .ToList<Article>();          
    
            </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> ResultObject
            {
                state </span>=<span style="color: #000000;"> ResultState.Success,
                result </span>=<span style="color: #000000;"> articles
            };
        }      </span></pre>
    
    复制代码

    此时,如果需要,仍可以通过tockenid获取用户信息。

    四、关于访问的权限

    此时用户需要登陆才能访问受限业务Api,但对用户权限并没有约束,实际应用时需要建立角色,通过用户于角色对应关系和角色与资源的对应关系,确认用户可以访问的资源列表。

    五、几点需要优化的地方

    这里描述了通过TockenID进行用户认证的基本思路,实际应用时还有很多需要改善的地方:

    1、对于一些公开应用是不需要验证的,如果在中间件中通过if来判断路径就显得比较丑陋,是否可以通过给这些Controller加上相关的特性来进行标识?

    2、如何方便地判断用户与资源的对应关系?

    3、Controller中通过tockenid获取用户信息的方法能否封装一下?

    这些问题暂时还没有考虑充分,以后有机会完善一下。

  • 相关阅读:
    hystrix总结之缓存
    python3列表
    hystrix总结之多返回值命令
    hystrix总结之限流
    hystrix(5) 延时检测
    redis-start
    设计模式-4建造者模式
    设计模式-3原型模式
    设计模式-2工厂设计模式
    设计模式-七大设计原则
  • 原文地址:https://www.cnblogs.com/owenzh/p/11207828.html
Copyright © 2020-2023  润新知