上一篇: 【WEB API项目实战干货系列】- 接口文档与在线测试(二)

这篇我们主要来介绍我们如何在API项目中完成API的登录及身份认证. 所以这篇会分为两部分, 登录API, API身份验证.

这一篇的主要原理是: API会提供一个单独的登录API, 通过用户名,密码来产生一个SessionKey, SessionKey具有过期时间的特点, 系统会记录这个SessionKey, 在后续的每次的API返回的时候,客户端需带上这个Sessionkey, API端会验证这个SessionKey.

登录API

我们先来看一下登录API的方法签名

SessionObject是登录之后,给客户端传回的对象, 里面包含了SessionKey及当前登录的用户的信息

这里每次的API调用,都需要传SessionKey过去, SessionKey代表了用户的身份信息,及登录过期信息。

登录阶段生成的SessionKey我们需要做保存,存储到一个叫做UserDevice的对象里面, 从语意上可以知道用户通过不同的设备登录会产生不同的UserDevice对象.

最终的登录代码如下:

[RoutePrefix("api/accounts")]public class AccountController : ApiController{private readonly IAuthenticationService _authenticationService = null;public AccountController(){//this._authenticationService = IocManager.Intance.Reslove<IAuthenticationService>();}[HttpGet]public void AccountsAPI(){}/// <summary>/// 登录API/// </summary>/// <param name="loginIdorEmail">登录帐号(邮箱或者其他LoginID)</param>/// <param name="hashedPassword">加密后的密码,这里避免明文,客户端加密后传到API端</param>/// <param name="deviceType">客户端的设备类型</param>/// <param name="clientId">客户端识别号, 一般在APP上会有一个客户端识别号</param>/// <remarks>其他的登录位置啥的,是要客户端能传的东西,都可以在这里扩展进来</remarks>/// <returns></returns>[Route("account/login")]public SessionObject Login(string loginIdorEmail, string hashedPassword, int deviceType = 0, string clientId = ""){if (string.IsNullOrEmpty(loginIdorEmail))throw new ApiException("username can't be empty.", "RequireParameter_username");if (string.IsNullOrEmpty(hashedPassword))throw new ApiException("hashedPassword can't be empty.", "RequireParameter_hashedPassword");int timeout = 60;var nowUser = _authenticationService.GetUserByLoginId(loginIdorEmail);if (nowUser == null)throw new ApiException("Account Not Exists", "Account_NotExits");#region Verify Passwordif (!string.Equals(nowUser.Password, hashedPassword)){throw new ApiException("Wrong Password", "Account_WrongPassword");}#endregionif (!nowUser.IsActive)throw new ApiException("The user is inactive.", "InactiveUser");UserDevice existsDevice = _authenticationService.GetUserDevice(nowUser.UserId, deviceType);// Session.QueryOver<UserDevice>().Where(x => x.AccountId == nowAccount.Id && x.DeviceType == deviceType).SingleOrDefault();if (existsDevice == null){string passkey = MD5CryptoProvider.GetMD5Hash(nowUser.UserId + nowUser.LoginName + DateTime.UtcNow.ToString() + Guid.NewGuid().ToString());existsDevice = new UserDevice(){UserId = nowUser.UserId,CreateTime = DateTime.UtcNow,ActiveTime = DateTime.UtcNow,ExpiredTime = DateTime.UtcNow.AddMinutes(timeout),DeviceType = deviceType,SessionKey = passkey};_authenticationService.AddUserDevice(existsDevice);}else{existsDevice.ActiveTime = DateTime.UtcNow;existsDevice.ExpiredTime = DateTime.UtcNow.AddMinutes(timeout);_authenticationService.UpdateUserDevice(existsDevice);}nowUser.Password = "";return new SessionObject() { SessionKey = existsDevice.SessionKey, LogonUser = nowUser };}}

API身份验证

身份信息的认证是通过Web API 的 ActionFilter来实现的, 每各需要身份验证的API请求都会要求客户端传一个SessionKey在URL里面丢过来。

在这里我们通过一个自定义的SessionValidateAttribute来做客户端的身份验证, 其继承自 System.Web.Http.Filters.ActionFilterAttribute, 把这个Attribute加在每个需要做身份验证的ApiControler上面,这样该 Controller下面的所有Action都将拥有身份验证的功能, 这里会存在如果有少量的API不需要身份验证,那该如何处理,这个会做一些排除,为了保持文章的思路清晰,这会在后续的章节再说明.

public class SessionValidateAttribute : System.Web.Http.Filters.ActionFilterAttribute{public const string SessionKeyName = "SessionKey";public const string LogonUserName = "LogonUser";public override void OnActionExecuting(HttpActionContext filterContext){var qs = HttpUtility.ParseQueryString(filterContext.Request.RequestUri.Query);string sessionKey = qs[SessionKeyName];if (string.IsNullOrEmpty(sessionKey)){throw new ApiException("Invalid Session.", "InvalidSession");}IAuthenticationService authenticationService = IocManager.Intance.Reslove<IAuthenticationService>();//validate user sessionvar userSession = authenticationService.GetUserDevice(sessionKey);if (userSession == null){throw new ApiException("sessionKey not found", "RequireParameter_sessionKey");}else{//todo: 加Session是否过期的判断if (userSession.ExpiredTime < DateTime.UtcNow)throw new ApiException("session expired", "SessionTimeOut");var logonUser = authenticationService.GetUser(userSession.UserId);if (logonUser == null){throw new ApiException("User not found", "Invalid_User");}else{filterContext.ControllerContext.RouteData.Values[LogonUserName] = logonUser;SetPrincipal(new UserPrincipal<int>(logonUser));}userSession.ActiveTime = DateTime.UtcNow;userSession.ExpiredTime = DateTime.UtcNow.AddMinutes(60);authenticationService.UpdateUserDevice(userSession);}}private void SetPrincipal(IPrincipal principal){Thread.CurrentPrincipal = principal;if (HttpContext.Current != null){HttpContext.Current.User = principal;}}}

OnActionExcuting方法:

这个是在进入某个Action之前做检查, 这个时候我们刚好可以同RequestQueryString中拿出SessionKey到UserDevice表中去做查询,来验证Sessionkey的真伪, 以达到身份验证的目的。

用户的过期时间:

在每个API访问的时候,会自动更新Session(也就是UserDevice)的过期时间, 以保证SessionKey不会过期,如果长时间未更新,则下次访问会过期,需要重新登录做处理。

Request.IsAuthented:

上面代码的最后一段SetPrincipal就是来设置我们线程上下文及HttpContext上下文中的用户身份信息, 在这里我们实现了我们自己的用户身份类型

public class UserIdentity<TKey> : IIdentity{public UserIdentity(IUser<TKey> user){if (user != null){IsAuthenticated = true;UserId = user.UserId;Name = user.LoginName.ToString();DisplayName = user.DisplayName;}}public string AuthenticationType{get { return "CustomAuthentication"; }}public TKey UserId { get; private set; }public bool IsAuthenticated { get; private set; }public string Name { get; private set; }public string DisplayName { get; private set; }}public class UserPrincipal<TKey> : IPrincipal{public UserPrincipal(UserIdentity<TKey> identity){Identity = identity;}public UserPrincipal(IUser<TKey> user): this(new UserIdentity<TKey>(user)){}/// <summary>/// /// </summary>public UserIdentity<TKey> Identity { get; private set; }IIdentity IPrincipal.Identity{get { return Identity; }}bool IPrincipal.IsInRole(string role){throw new NotImplementedException();}}public interface IUser<T>{T UserId { get; set; }string LoginName { get; set; }string DisplayName { get; set; }}

这样可以保证我们在系统的任何地方,通过HttpContext.User 或者 System.Threading.Thread.CurrentPrincipal可以拿到当前线程上下文的用户信息, 方便各处使用

加入身份认证之后的Product相关API如下:

[RoutePrefix("api/products"), SessionValidate]public class ProductController : ApiController{[HttpGet]public void ProductsAPI(){ }/// <summary>/// 产品分页数据获取/// </summary>/// <returns></returns>[HttpGet, Route("product/getList")]public Page<Product> GetProductList(string sessionKey){return new Page<Product>();}/// <summary>/// 获取单个产品/// </summary>/// <param name="productId"></param>/// <returns></returns>[HttpGet, Route("product/get")]public Product GetProduct(string sessionKey, Guid productId){return new Product() { ProductId = productId };}/// <summary>/// 添加产品/// </summary>/// <param name="product"></param>/// <returns></returns>[HttpPost, Route("product/add")]public Guid AddProduct(string sessionKey, Product product){return Guid.NewGuid();}/// <summary>/// 更新产品/// </summary>/// <param name="productId"></param>/// <param name="product"></param>[HttpPost, Route("product/update")]public void UpdateProduct(string sessionKey, Guid productId, Product product){}/// <summary>/// 删除产品/// </summary>/// <param name="productId"></param>[HttpDelete, Route("product/delete")]public void DeleteProduct(string sessionKey, Guid productId){}

可以看到我们的ProductController上面加了SessionValidateAttribute, 每个Action参数的第一个位置,加了一个string sessionKey的占位, 这个主要是为了让Swagger.Net能在UI上生成测试窗口

这篇并没有使用OAuth等授权机制,只是简单的实现了登录授权,这种方式适合小项目使用.

这里也只是实现了系统的登录,API访问安全,并不能保证 API系统的绝对安全,我们可以透过 路由的上的HTTP消息拦截, 拦截到我们的API请求,截获密码等登录信息, 因此我们还需要给我们的API增加SSL证书,实现 HTTPS加密传输。

另外在前几天的有看到结合客户端IP地址等后混合生成 Sessionkey来做安全的,但是也具有一定的局限性, 那种方案合适,还是要根据自己的实际项目情况来确定.

由于时间原因, 本篇只是从原理方面介绍了API用户登录与访问身份认证,因为这部分真实的测试设计到数据库交互, Ioc等基础设施的支撑,所以这篇的代码只能出现在SwaggerUI中,但是无法实际测试接口。在接下来的代码中我会完善这部分.

代码: 代码下载(代码托管在CSDN Code)

【WEB API项目实战干货系列】- API登录与身份验证(三)相关推荐

  1. 【WEB API项目实战干货系列】- API访问客户端(WebApiClient适用于MVC/WebForms/WinForm)(四)

    目前最新的代码已经通过Sqlite + NHibernate + Autofac满足了我们基本的Demo需求. 按照既定的要求,我们的API会提供给众多的客户端使用, 这些客户端可以是各种Web站点, ...

  2. 【WEB API项目实战干货系列】- WEB API入门(一)

    这篇做为这个系列的第一篇,做基本的介绍,有经验的人可以直接跳到第二部分创建 ProductController. 创建 Web API 项目 在这里我们使用VS2013, .NET 4.5.1创建一个 ...

  3. 【WEB API项目实战干货系列】- 接口文档与在线测试(二)

    这一篇我们主要介绍如何做API帮助文档,给API的调用人员介绍各个 API的功能, 输入参数,输出参数, 以及在线测试 API功能(这个也是方便我们自己开发调试) 我们先来看看我们的API最终帮助文档 ...

  4. 视频教程-Ruby on Rails打造企业级RESTful API项目实战我的云音乐-Ruby/Rails

    Ruby on Rails打造企业级RESTful API项目实战我的云音乐 任苹蜻,爱学啊创始人 & CEO,曾就职于某二车手公司担任Android工程师后离职创办爱学啊,我们的宗旨是:人生 ...

  5. 菜鸟级三层框架(EF+MVC)项目实战之 系列一 EF零基础创建领域模型

    系列概述:全系列会详细介绍抽象工厂三层的搭建,以及EF高级应用和 ASP.NET MVC3.0简单应用,应用到的技术有Ef.Lambda.Linq.Interface.T4等. 由于网上对涉及到的技术 ...

  6. Selenium Web 自动化 - 项目实战(三)

    Selenium Web 自动化 - 项目实战(三) 2016-08-10 目录 1 关键字驱动概述 2 框架更改总览 3 框架更改详解   3.1 解析新增页面目录   3.2 解析新增测试用例目录 ...

  7. OAuth2.0项目实战干货(亚马逊平台对接)

    消息队列的对比-Auth2.0项目实战-亚马逊平台对接 一 OAuth2.0介绍 二 开发背景 三 Auth2.0项目实战***干货 Auth2.0数据流 亚马逊平台: 作为外部第三方平台 内部应用平 ...

  8. WEB前端项目实战/酒仙网开发-李强强-专题视频课程

    WEB前端项目实战/酒仙网开发-204人已学习 课程介绍         WEB前端项目实战/酒仙网开发 课程收益     WEB前端项目实战/酒仙网开发 讲师介绍     李强强 更多讲师课程    ...

  9. vue 3 项目实战一(绘制登录界面)

    目录 一.概述 二.创建vue项目 三.需求分析 四.构建组件 五.vue组件之间的通信 一.概述 本文记录了项目实现的详细步骤以及原理,十分适合初学vue的萌新练手,也是阶段性学习的一个总结,可能会 ...

最新文章

  1. 1、MySQL视图是什么?
  2. Java GC垃圾回收机制
  3. POJ 3080 Blue Jeans (多个字符串的最长公共序列,暴力比较)
  4. 第五章 MongoDb索引优化 5.6
  5. Lombok,一种编译时Java注释预处理器,可最大程度地减少代码大小
  6. void函数调用时显示不允许使用不完整的_4位数码管显示模块驱动
  7. 基于卷积神经网络的手写数字识别、python实现
  8. leetcode 旋转数组
  9. cocos2d 入门必备4个基本概念
  10. JavaScript parseInt() 函数
  11. 快、准、狠!秒杀Excel的报表工具,十分钟教你做好数据填报
  12. LINQ体验(2)——C# 3.0新语言特性和改进(上篇)
  13. 自动优化Windows Xp批处理文件
  14. BeX5企业快速开发平台-前端快速开发框架
  15. 《金字塔原理》学习笔记 | 第1篇—表达的逻辑
  16. 《自己动手写网络爬虫》读书笔记——队列与集合
  17. Ubuntu16.040如何解决安装网易云音乐后第二次开机打不开问题
  18. 用C#设计在局域网发送短信的程序
  19. 解决Kangle的Easypanel控制面板用户前台php版本无法切换的解决方法
  20. linux下如何查看hdmi设备,如何在Linux中设置HDMI数字播放 | MOS86

热门文章

  1. In Gradle projects, always use http://schemas.andr
  2. 了解黑客的关键工具---揭开Shellcode的神秘面纱
  3. 【SQL Server】系统学习之一:表表达式
  4. 线性代数与矩阵论 定理 1.5.6 拉格朗日插值公式
  5. MySQL5.5复制新特性
  6. 数据连接池的工作机制是什么?
  7. 别得意,你只是假装收藏了而已
  8. http协议进阶(三)http报文
  9. flask 核心 之 应用上下文 及 请求上下文
  10. 微软发布Visual Studio 2017 15.8