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

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

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

登录API

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

image

 

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

image

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

 

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

image

 

最终的登录代码如下:

复制代码
[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上生成测试窗口

image

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

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

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

 

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


文章转载出自 :http://www.cnblogs.com/Flyear/p/4875066.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/286493.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql数据库建立的数据库在哪个文件夹?

为什么80%的码农都做不了架构师&#xff1f;>>> 一般在安装目录下的data文件夹下&#xff0c;或者在C:\Documents and Settings\All Users\Application Data\MySQL\MySQL Server 5.1\data&#xff08;你的可能是C:\Documents and Settings\All Users\Application D…

ArcGIS实验教程——实验二十四:人口密度制图

ArcGIS实验视频教程合集:《ArcGIS实验教程从入门到精通》(附配套实验数据)》 一、实验分析 人口密度是指单位土地面积上居住的人口数,通常以每平方千米或每公顷内的常住人口为单位计算。人口密度同资源、经济密切结合,因此,科学准确地分析人口密度的分布情况,对合理制定…

Navicat 远程连接ubuntu出现的问题

2003-Cantt connect to Mysql server to xxxxxxx 解决&#xff1a; vim /etc/mysql/my.cnf 修改bind-address 0.0.0.0 然后重启mysql&#xff1a; 这时进入mysql可能会报错&#xff1a; ERROR 2002 (HY000): Cant connect to local MySQL server through socket /v…

WPF效果第一百八十八篇之再玩Expander

大端午节的在屋里吹着空调撸着代码真是酸爽;闲话也不多扯,直接看今天要分享的效果:1、关于简单的布局设计:2、前台先来个死布局,回头ListBox改模板:<Expander ExpandDirection"Left" Header"控制卡" VerticalAlignment"Bottom" HorizontalAli…

Android之实现长按Webview页面文字自定义复制、全选、分享、搜索、翻译功能(支持多语言,博文也有Demo下载地址)

1 需求和效果爆照 浏览器app封装了Webview,然后实现实现长按Webview页面文字自定义复制、全选、分享、搜索、翻译功能(支持多语言),都在自己的浏览器app里面进行搜索和翻译,不跳到系统浏览器里面去 效果爆照如下,oppo手机效果如下 华为手机效果如下 2 Demo下载地址 De…

中国西北地区专题地图合集(高清)

1. 西北地区概况图 2. 西北地区植被类型分布图 3. NDVI变化趋势图 4. 气候与NDVI的相关性

使用putty连接linux

使用putty连接linux 快照的使用 &#xff0c;做快照相当于做备份&#xff0c;比如配置好IP&#xff0c;快照一下&#xff0c;下次就可以在回到这里&#xff01; putty下载 最好去官网下载 下载putty.zip如图所示 如何使用putty 如图设置好IP然后 save 保存 如…

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

目前最新的代码已经通过Sqlite NHibernate Autofac满足了我们基本的Demo需求. 按照既定的要求&#xff0c;我们的API会提供给众多的客户端使用, 这些客户端可以是各种Web站点, APP, 或者是WinForm, WPF, Silverlight等诸如此类的应用&#xff0c;将来还有可能是各种Iot等物联…

【GIS风暴】GIS拓扑关系原理详解

目 录 1. 拓扑关系的概念2. 拓扑元素3. 拓扑关系4. 拓扑关系的意义5. 拓扑在ArcGIS中实现1. 拓扑关系的概念 地图上的拓扑关系是指图形在保持连续状态下的变形(缩放、旋转和拉伸等),但图形关系不变的性质。 2. 拓扑元素 对二维而言,矢量数据可抽象为点(节点)、线(链、…

Android之简单的文件夹选择器实现

1、效果爆照 2、代码实现 前提需要保证app有读写权限 activity_select_folder.xml文件如下 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layo…

【iVX 初级工程师培训教程 10篇文拿证】04 画布及我和 iVX 合照

目录 【iVX 初级工程师培训教程 10篇文拿证】01 了解 iVX 完成新年贺卡 【iVX 初级工程师培训教程 10篇文拿证】02 数值绑定及自适应网站制作 【iVX 初级工程师培训教程 10篇文拿证】03 事件及猜数字小游戏 【iVX 初级工程师培训教程 10篇文拿证】04 画布及我和 iVX 合照 【iV…

360极速浏览器使用postman

步骤如下&#xff1a;1、将crx文件打包成zip文件2、解压打包的zip文件&#xff0c;并将_metadata文件夹修改为metadata3、打开360浏览器的扩展4、360浏览器加载postman插件5、创建快捷方式6、双击快捷方式打开postman下载地址&#xff1a;http://pan.baidu.com/s/1c1ZX8XE如果网…

聊聊 C++ 和 C# 中的 lambda 玩法

这几天在看 C 的 lambda 表达式&#xff0c;挺有意思&#xff0c;这个标准是在 C11标准 加进去的&#xff0c;也就是 2011 年&#xff0c;相比 C# 2007 还晚了个 4 年&#xff0c; Lambda 这东西非常好用&#xff0c;会上瘾&#xff0c;今天我们简单聊一聊。一&#xff1a;语法…

Windows10系统重装后必不可少的优化步骤

1. 查看系统的激活状态 Win+R,打开运行,输入slmgr.vbs -xpr,回车! 可以看到,该系统没有永久激活,即将过期,过期后部分功能会不可使用,需要重新激活。 2. 彻底关掉Windows Defender 方法一: 打开“命令提示符(管理员)”,然后输入: reg add "HKEY_LOCAL_MA…

【iVX 初级工程师培训教程 10篇文拿证】03 事件及猜数字小游戏

目录 【iVX 初级工程师培训教程 10篇文拿证】01 了解 iVX 完成新年贺卡 【iVX 初级工程师培训教程 10篇文拿证】02 数值绑定及自适应网站制作 【iVX 初级工程师培训教程 10篇文拿证】03 事件及猜数字小游戏 【iVX 初级工程师培训教程 10篇文拿证】04 画布及我和 iVX 合照 【iV…

visual studio系列(vs)启动调试网站使用ip+端口局域网访问

vs系列工具创建web应用时会自动创建一些配置文件&#xff0c;本文章讲的是如何修改配置文件&#xff0c;使vs在启动运行调试情况下 使用ip端口形式在局域网访问。本文章使用的是vs2015&#xff0c;.net 4.5。步骤如下&#xff1a;1.使用vs创建好你的web应用&#xff0c;打开项目…

Android studio之编译提示Could not find :umeng-asms-v1.2.1:.

1 、问题 Could not determine the dependencies of task :app:compileDebugJavaWithJavac. > Could not resolve all task dependencies for configuration :app:debugCompileClasspath.> Could not find :umeng-asms-v1.2.1:.Required by:project :app> Could not …

WPF效果第一百八十九篇之又玩Expander+ListBox

上一篇文章已经提前预告了今天要分享的效果,今天接着上一篇的效果接着去实现,还是先来看最终实现的效果:1、关于简单的布局设计:总结&#xff1a;①③是Expander②④⑤⑥是ListBox2、把上一篇的②这一块用ListBox替换:<Expander Grid.Column"1" ExpandDirection&q…

作为一个甘肃天水人,我对罐罐茶有一种特殊的情怀

作为一个出生在罐罐茶世家的80后乡土人&#xff0c;经历了罐罐茶的发展演变历史&#xff0c;与罐罐茶结下了不解情缘&#xff0c;下面我就跟大家分享一下刘一哥与罐罐茶的故事吧。 久违的罐罐茶.mp4美丽邂逅 我最早接触罐罐茶应该是90年代&#xff0c;那个年代家里条件很不好&…

【iVX 初级工程师培训教程 10篇文拿证】02 数值绑定及自适应网站制作

目录 【iVX 初级工程师培训教程 10篇文拿证】01 了解 iVX 完成新年贺卡 【iVX 初级工程师培训教程 10篇文拿证】02 数值绑定及自适应网站制作 【iVX 初级工程师培训教程 10篇文拿证】03 事件及猜数字小游戏 【iVX 初级工程师培训教程 10篇文拿证】04 画布及我和 iVX 合照 【iV…