系列目录html
按部就班学.Net Core Web Api开发系列目录git
本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApigithub
1、概述web
本篇介绍Web系统的应用安全,主要涉及用户的身份认证和访问权限问题。数据库
大部分web应用习惯采用Session来保存用户认证信息,对于WebApi而言,调用者不必定是Web浏览器,多是Android、iOS客户端,多是微信小程序,也多是客户端程序等等,这些客户端模拟构造cookie、存储或传递sessionid都不是太方便,这种状况下,采用令牌(tockenid)的方式进行受权管理就显得比较方便,惟一不方便的就是每次调用都要传递tockenid。json
基本流程以下:小程序
一、调用登录接口,经过正确的用户名和密码活动TockenID;微信小程序
二、经过TockenID调用其余业务接口。api
2、基本使用浏览器
一、处理用户登录的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。
二、在业务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 }; }
3、采用中间件进行用户认证
由于每一个业务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获取用户信息。
4、关于访问的权限
此时用户须要登录才能访问受限业务Api,但对用户权限并无约束,实际应用时须要创建角色,经过用户于角色对应关系和角色与资源的对应关系,确认用户能够访问的资源列表。
5、几点须要优化的地方
这里描述了经过TockenID进行用户认证的基本思路,实际应用时还有不少须要改善的地方:
一、对于一些公开应用是不须要验证的,若是在中间件中经过if来判断路径就显得比较丑陋,是否能够经过给这些Controller加上相关的特性来进行标识?
二、如何方便地判断用户与资源的对应关系?
三、Controller中经过tockenid获取用户信息的方法可否封装一下?
这些问题暂时尚未考虑充分,之后有机会完善一下。