本篇文章将从无到完整的登录框架或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. codevs 1299 切水果 线段树

    1299 切水果  时间限制: 1 s  空间限制: 128000 KB     题目描述 Description 简单的说,一共N个水果排成一排,切M次,每次切[L,R]区间的所有水果(可能有的水果 ...

  2. Echarts 地图(map)插件之 鼠标HOVER和tooltip自定义数据

    在项目开发中,有需要用到地图的地方,百度的echarts地图插件就是个不错的选择, 这里总结一下地图自定义鼠标HOVER时的事件和自定义tooltip数据: 一.鼠标HOVER时的事件: 参照官方文档 ...

  3. 1.start

    1. react-native init Helloworld  // 创建 helloworld 工程 2. 进入 helloworld ->android, 运行 react-navite ...

  4. 问题杂烩(scrollTop/背景透明度动画)

    今天给同学找我帮忙写js,是公司里的活..我是不是应该跟他要钱哈哈,不过一顿饭肯定是免不了的了. 言归正传,今天写了三个小东西,因为兼容性的问题,用jq写的(很是别扭的说,但是没办法啊,一边看api一 ...

  5. GeoServer基础教程(二):GeoServer的Web管理界面快速入门

    转载:http://blog.163.com/daimiao_study/blog/static/248923117201542522742373/ GeoServer的控制和管理是基于网页形式,所有 ...

  6. 13 Python 函数进阶

    代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间 命名空间和作用域 命名空间的本质:存放名字与值的绑定关系 >>> ...

  7. codeforces 653A A. Bear and Three Balls(水题)

    题目链接: A. Bear and Three Balls time limit per test 2 seconds memory limit per test 256 megabytes inpu ...

  8. redis学习--Hashes数据类型

    本文转自:http://www.cnblogs.com/stephen-liu74/archive/2012/03/19/2352932.html 一.概述: 我们可以将Redis中的Hashes类型 ...

  9. Centos6.5 安装pip

    1.下载 sudo wget https://bootstrap.pypa.io/get-pip.py --no-check-certificate 2.安装  python get-pip.py 参 ...

  10. Azure自动化部署服务 (1)

    Azure中已经发布了自动化部署服务的PaaS功能. 本文将介绍自动化服务Automation初始化过程. 在Azure Management Portal上左边可以看到Azure的各种服务,其中一项 ...