本篇文章将从无到完整的登录框架或API详细讲述登录令牌原理、攻略等安全点。

有些协议或框架也喜欢把令牌叫票据(Ticket),不论是APP还是Web浏览器,很多框架或协议都用到了本文所说的这套类似的认证机制(客户端各种加密用户名密码当我没说),这里的以Asp.net core下Web登录和验证为例子进行讲述,但原理攻略和语言、框架都无关。

目录:

一、过程与原理

二、Demo数据库结构

三、Demo源码介绍

四、构建与验证Token

五、Token失效与登录唯一性

六、CAS/SSO单点登录

七、URL授权验证与扫码登录

八、Session实现

九、关于Token刷新

本片文章Demo:https://github.com/chaoyebugao/AcctAuthDemo

一、过程与原理

 

令牌机制简单过程(点击查看大图)

首先,这套机制使用场景是登录授权和身份验证,可以用在Web上,也可以用在API的访问控制上。这套机制其实和很多无状态框架登录/授权验证协议类似,这里讲的其实和OAuth2.0里面授权码模式的原理是一样的(authorization code),只不过我们在这里将其步骤拆分,了解其原理和实现,以后搭建项目应用才能庖丁解牛。还有一点,很多框架的授权机制都太繁重且并不能灵活应用,这时候就可以自己搭一个。

首先,用户使用终端向服务器提供可信凭证(一般登录是用户名密码,微信公众平台是appid+appsecret),服务端确认凭证正确,则返回授权的令牌(以下称Token)。这个Token是随机的字符串且与本次授权唯一相关。返回Token给终端的同时服务端也要一并保存Token,这样终端和服务端都只认Token,终端所有请求发送都需要携带此Token,服务端会验证和控制此Token。此时Token就有两个,一个是终端Token,一个是服务端Token,其中一个不对或没有,服务端都是拒绝的。

举个例子,你上12306购票,购买过程就是授权你Token的过程,你的纸质票就是Token,另外一半对应的Token保存在12306那的DB里头,所有门闸就是网关,当你过门闸时会验证你Token是否对应DB的Token。你下车后,12306就把DB的Token标记处理掉,这样服务端就不会再认你手上的纸质票,票也就作废了。

围绕这一机制,我们将讲述CAS单点登录、令牌授权与身份验证、Session实现、防重放攻击、登录唯一性、URL授权验证(用于验证邮箱等)等

二、Demo数据库结构

设备表:用于识别、记录不同的设备,同一设备应该有唯一的标记Id,下面详说

令牌表:用于持久化令牌,ExpireAt为过期时间,Token即令牌字符串,根据UserId与用户表相关联,根据DeviceId与设备表相关联

用户表:用户表,保存用户名密码等

设备表和设备标记(DeviceId)是可有可无的,可以根据实际业务来处理,有必要的话再增加其他相关联的数据和表。C/S或App的话DeviceId可以用系统的标识Id,像Web浏览器的话因为拿不到类似的东西,我们可以指定一个在Cookie里头,Demo就是这么干的。

三、Demo源码介绍

UserController - 用户注册、登录、注销登录

HomeController - Index - 默认启动页,Token验证页

四、构建与验证Token

构建Token

验证Token

Token的构建发生在用户提供的凭证(如用户名密码)被服务端确认无误之后。一次登录/授权的Token分两部分,服务端持有的我们叫数据库Token,用户端(Endpoint)持有的叫终端Token。终端Token可以是任意的随机字符串构成,所以这里最后要根据登录情况来求得哈希值即终端Token本身。因为后面要根据终端Token来查询处理数据库Token记录,所以他们必须有种关联,这种关联就是如上图所示,终端Token+设备Id得到的哈希值即数据库Token本身。

可以看出,整个生成过程是单向不可逆的,验证也只能是单向验证,所以生成关系是这样的:

授权Token构建关系图

这里有几点要注意的:

  • 终端Token应该有足够的长度,且每次应随机生成,因此才有Guid.NewGuid()参与求值
  • 终端Token参与生成的userId、name是起到了盐作用,让整个构建更加复杂(经提醒已经排除掉了密码的参与,哈希虽然很难破解但还是谨慎点好)
  • 不论是终端Token还是数据库Token都不应该可逆加密处理任何内容,因为可解密的话不论是终端还是数据库数据泄露的,都有被破解的风险,所以用哈希求值是最合适的
  • 构建数据库Token有deviceId参与,这样每次Token就只能是对应的deviceId才能被验证,这样就起了绑定作用。除了deviceId还可以绑定其他场景相关的,比如IP地址、终端类型
  • 日志最好不要记录任何Token

两部分Token构建好之后,终端Token将被返回给终端,数据库Token持久化到服务端中。终端和数据库都要将各自的Token和场景信息持久化,Demo里面终端Token和deviceId放到了Cookie中。每次请求的终端都需要提交终端Token和绑定用的场景信息(deviceId),因为验证的时候数据库Token保存的是由它们哈希过来的值,因此验证的时候也是使用一样的构建过程(即Demo里面的BuildDatabaseToken方法),这样终端Token和数据库Token就有了对应关系。得到数据库Token就能在数据库里面查找了(即上图的loginTokenRepository.FindUser方法)。Demo的验证页面是Home/Index,里面使用了过滤器CheckLoginTokenActionFilterAttribute做验证,在需要验证的Controller或Action上做ServiceFilter属性标记处理即可。

这里有几点要注意的:

  • 如果使用Http做接口且有App接入,不方便地支持Cookie机制的话可以改为放在请求头中
  • 如果使用Http且为Web浏览器,终端Token保存的Cookie应该设为HttpOnly,让JS不可触碰

到这里童鞋们知道为什么Token拆成两部分了吗?整个Token授权过程是单向不可逆的,而且每个用户都有自己的哈希盐来生成Token,这样能避免哈希值被批量暴力破解,即使终端Token和数据库Token都泄露了你也对应不上。试想一下如果不是这样而是终端数据库的Token是相同的,那一旦数据库泄露那么黑客就能模拟Token进行登录/授权了。另外数据库Token哈希过后长度变短,查询性能也能提高,毕竟每个请求都需要进行验证,查询频率是很高的。

五、Token失效与登录唯一性

不论是终端Token还是服务端Token都要有失效机制,时间越短越安全,但也要结合使用场景需求来设定时长。终端Token如果是Cookie的话直接用Cookie的过期时间即可,并且要和数据库Token的过期时间一致。数据库Token生成的时候也要指定过期时间,Demo里面数据库保存的字段为ExpireAt。一般有以下几种失效情况:

  • 到了过期时间
  • 用户修改账户关键信息,服务端需要主动将旧的Token全部作废掉,如修改密码
  • 用户注销登录
  • 用户使用Token刷新机制

另外类似的,如果需求是只能一种终端一个登录,比如Web和App可以保持同时登录但App只能有一个登录,数据库Token还得绑定“终端类型”,这样在最新一次登录的时候把相同的终端类型的旧的数据库Token全部作废掉就好了。如果账号只能有一个登录,那什么都不绑定,同一时间只保持最新一个Token有效即可。

可以看出,服务端的保有的数据库Token可以有效控制其授权,达到访问控制的目的。

六、CAS/SSO单点登录

CAS即中央认证服务,SSO即单点登录。很多时候这两个会放在一起说,其实CAS是一套解决方案,SSO是一种机制描述。如果我们使用的是Http-Web那么我们如何实现我们自己的SSO呢?很简单,把Token和绑定的场景信息提升到同一个域下即可。比如有总部和门店两个系统分别使用了hq.xxxx.com/store.xxxx.com子域名,那不管从哪个系统登录,login_token和deviceId这两个Cookie放在顶级域.xxxx.com下即可,这样所有子系统都能访问得到它们,继而都保有登录/授权状态。有没有发现登录新浪微博后,输入weibo.com都会先跳转到sso然后再跳转回来,这个也差不多,这也是为什么你登录了新浪微博,你新浪博客也是登录了的状态。

七、URL授权验证与扫码登录

当我们需要进行邮箱验证的时候,有可能是用户登录和邮箱不是一个终端的,这时候我们就需要进行URL授权验证来避免用户再次进行登录。其原理很简单,在用户点击验证的链接上面附上URL授权令牌即可(下面简称URL Token),这个URL Token与登录Token不应该有关系所以应当单独保存。生成一个URL Token,服务端再对应保存类似的服务端Token,这样就有了【URL Token】 - 【服务端Token】 - 【用户】这样的对应关系。当用户在有效期内点击后,服务端获得URL Token也就能进行授权或验证。

扫码登录的场景复杂一些,终端生成的二维码其实就是一个Token(我们称之为QR Token)这个Token是和终端绑定的。用户拿App扫了QR码,其实就是在App内同时提交QR Token和用户信息,用户确认可以登录后服务端会颁发登录Token给终端,这样终端就是登录状态了,这一步也就是上面构建和验证登录Token的过程。实际扫码登录需要实现即时通讯,这样终端才能做出相应的反应。另外QR Token也是一样有过期时间的,因此那些扫码登录的页面会做二维码自动刷新的。

八、Session实现

其实有些童鞋会纳闷,完善的框架都会提供Session操作,其原理是一样的,那为什么我们还这么“造作”呢?原因有二,框架自带的可能过重,比如我就很不喜欢asp.net自带的授权认证机制,微软弄得一套一套的,简直就是全家桶,笨重,自己实现一个能定制化且轻量。第二,考虑类似上面的功能实现,自己做能更灵活地实现。

我们已经实现了登录/授权和验证,接下来我们只要想办法把一些数据和Token绑定在一起,并放在缓存中,这些数据就是Session了。我一般的做法是封装一个SessionService,然后定义一套Session接口。一个Session数据由TokenKey-Value组成,如果Token失效,则清理所有对应的TokenKey数据即可。就是这么简单粗暴,不同的缓存组件实现不尽相同。

九、关于Token刷新

OAuth 2.0里面有提供Token刷新服务,即终端持有的Token快过期的时候,终端可以再调用刷新接口来替换快过期的Token,达到永续状态。简单来说就是请求新的Token,请求时旧Token作废掉,实现并不复杂,参见:Oauth2.0(三):Access Token 与 Refresh Token

十、防重放攻击与签名机制

重放攻击(Replay Attacks)又叫重播攻击,防范这个其实和本文讨论的主题没关系。完整实现的接口都有实现,欲知详情,等我下一篇。

花了好几天来写了这篇文章,同时也是自己对这一技术点的总结归纳,有不对的地方还请指正。

相关链接:

ASP.NET Web API与Owin OAuth:调用与用户相关的Web API

微信公众平台技术文档 - 获取access_token

令牌Token和会话Session原理与攻略的更多相关文章

  1. MyBatis之会话Session原理

    MyBatis 之会话 Session 执行逻辑 1.SQL 会话工厂构建器类 SqlSessionFactoryBuilder 的 build 方法用于构建 SqlSessionFactory 类的 ...

  2. NAT地址转换原理全攻略

    NAT转换方式及原理 在NAT的应用中,可以仅需要转换内部地址(就是“内部本地址”转换成“内部全局地址”),这是最典型的应用,如内部网络用户通过NAT转换共享上网:也可以是仅需要转换外部地址(就是“外 ...

  3. session学习总结【session原理、应用、与cookie区别】

    session原理 session也是一种记录浏览器状态的机制,但与cookie不同的是,session是保存在服务器中. 由于http是无状态协议,当服务器存储了多个用户的session数据时,如何 ...

  4. php中session原理及安全性问题

    有一点我们必须承认,大多数web应用程序都离不开session的使用.这篇文章将会结合php以及http协议来分析如何建立一个安全的会话管理机制   我们先简单的了解一些http的知识,从而理解该协议 ...

  5. [转]PHP Session原理分析及使用

    之前在一个叫魔法实验室的博客中看过一篇<php session原理彻底分析>的文章,作者从session的使用角度很好阐述了在代码运行过程中,每个环节的变化以及相关参数的设置及作用.本来想 ...

  6. 从session原理出发解决微信小程序的登陆问题

    声明:本文为作者原创文章,转载请注明出处 https://www.cnblogs.com/MaMaNongNong/p/9127416.html  原理知识准备  对于已经熟悉了session原理的同 ...

  7. 使用SpringSession管理分布式系统的会话Session

    在我方供应链项目分布式部署的环境下,需要在统一网关服务中管理访问的Session,即无论访问请求路由到哪一个网关服务环境,使用的都是相同的HttpSession,这样就保证了在用户登录之后,能够使用统 ...

  8. puppet完全攻略(一)puppet应用原理及安装部署

    puppet完全攻略(一)puppet应用原理及安装部署 2012-06-06 18:27:56 标签:puppet puppet应用原理 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出 ...

  9. Spring Session原理解析

    前景提要: @EnableRedisHttpSession导入RedisHttpSessionConfiguration.classⅠ.被RedisHttpSessionConfiguration继承 ...

随机推荐

  1. Sqoop-1.4.4工具import和export使用详解

    转自:http://blog.csdn.net/wodatoucai/article/details/46343291 Sqoop可以在HDFS/Hive和关系型数据库之间进行数据的导入导出,其中主要 ...

  2. spring学习(1)

    struts是web框架(jsp/action/action) hibernate是orm框架,处于持久层. spring是一个框架,是容器框架.用于配置bean,并维护bean之间关系的一种框架. ...

  3. Unity3D连接WCF

    Unity3D连接WCF: 一.最简单的案例 1.VS2015中: (1)建立WCF应用服务程序ForUnity: (2)将自动生成的IService1.cs与Service1.svc删除:   (3 ...

  4. SmartGit(试用期30后),个人继续使用的方法。

    在我们做项目的过程中,我们会用到SmartGit这个软件来将本地的MAVEN项目push到国内的码云(https://git.oschina.net)或者是国外的github网站进行项目的管理,这个时 ...

  5. xxx was built without full bitcode" 编译错误解决

    xxx was built without full bitcode" 编译错误解决 iOS 打包上线 All object files and libraries for bitcode ...

  6. 【leetcode刷题笔记】Add Two Numbers

    You are given two linked lists representing two non-negative numbers. The digits are stored in rever ...

  7. thinkphp中图片上传的几种好的办法

    http://www.thinkphp.cn/code/701.html http://www.thinkphp.cn/code/151.html

  8. POJ1741 Tree(树的点分治基础题)

    Give a tree with n vertices,each edge has a length(positive integer less than 1001).Define dist(u,v) ...

  9. BZOJ4976: [Lydsy1708月赛]宝石镶嵌

    BZOJ4976: [Lydsy1708月赛]宝石镶嵌 https://lydsy.com/JudgeOnline/problem.php?id=4976 分析: 本来是从\(k\le 100\)这里 ...

  10. ACM学习历程—Hihocoder 1233 Boxes(bfs)(2015北京网赛)

    hihoCoder挑战赛12 时间限制:1000ms 单点时限:1000ms 内存限制:256MB   描述 There is a strange storehouse in PKU. In this ...