应用安全

本篇概述

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

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

  基本流程如下:

  1. 调用登陆接口,通过正确的用户名和密码活动TockenID;
  2. 通过TockenID调用其他业务接口。

1 基本使用

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 = Tocken.GetTockenID();
        _cache.Set(tockenid, user, new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(20)));
        return new ResultObject
        {
            state = ResultState.Success,
                result = tockenid
        };
    }
    catch(InvalidOperationException ex)
    {
        return new ResultObject
        {
            state = ResultState.Exception,
                ExceptionString = "未找到匹配的数据"
        };
    }
}

  首先在数据库寻找匹配的用户信息,如果验证成功就以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 < Article > articles = _context.Articles.AsNoTracking().ToList < Article > ();
    return new ResultObject
    {
        state = ResultState.Success,
            result = articles
    };
}

2 采用中间件进行用户认证

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

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

  该中间件直接截取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 > ();
    return new ResultObject
    {
        state = ResultState.Success,
            result = articles
    };
}

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

3 关于访问的权限

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

4 几点需要优化的地方

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

  1. 对于一些公开应用是不需要验证的,如果在中间件中通过if来判断路径就显得比较丑陋,是否可以通过给这些Controller加上相关的特性来进行标识?
  2. 如何方便地判断用户与资源的对应关系?
  3. Controller中通过tockenid获取用户信息的方法能否封装一下?

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

版权声明: 本文为智客工坊「seabluescn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

results matching ""

    No results matching ""