Token相关内容简述

Token的应用

Token是一种标志用户登录,保持用户状态的一种认证方法,令牌(临时)是服务端生成的一串字符串,作为客户端进行请求的一个标识。但是和Session id不同,其是通过特殊手段生成的,不同的服务器对应token的生成是不同的。

简单token的组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。

  • 访问令牌(Access token)表示访问控制操作主体的系统对象
  • 邀请码,在邀请系统中使用
  • 密保令牌(Security token),或者硬件令牌,例如U盾,或者叫做认证令牌或者加密令牌,一种计算机身份校验的物理设备
  • 会话令牌(Session token),交互会话中唯一身份标识符
  • 令牌化技术 (Tokenization), 取代敏感信息条目的处理过程

 

APP将用户输入的账号和密码提交给服务器;
(1)服务器对其进行校验,若账号和密码对得上则校验通过,说明登录成功。并生成一个token值,将其保存在数据库,同时也返回给客户端;

(2)客户端拿到返回的token值后,可将其保存在本地。作为公共参数,即以后每次请求服务器时都携带该token,提交给服务器,让服务器校验。

(3)服务器接收到请求后,会取出请求头里的token值与数据库存储的token进行对比校验。若两个token值相同,则说明用户登录成功过,且当前正处于登录状态,此时正常返回数据,让APP显示数据。若两个值不一致,则说明原来的的登录已经失效,此时返回错误状态码,提示用户跳转至登录界面重新登录。

App登录状态

可以在内存和磁盘存储一个登录状态,表示是否是登录状态。下次启动APP时,我只需要从本地获取该值,看其是登录状态否?然后决定界面怎么显示,或者点击界面上的按钮去执行什么事件,是去完成业务动作,还是弹出登录界面让用户重新登录。

常用的web上用户状态保持都是session或session+cookie,但是现在也常用token来实现该作用,特别是移动app的场景下,比较适合token。

session的缺点

如果使用单个服务器的话,用户过多的话,开销太大。如果我们系统采用分布式的话,我们登录时,响应我们的那台机器会记录我们登录信息,万一下一个请求,响应我们的不是原来那台机器的话,它并没有存储我们之前会话信息,就会认为我们并没有登录。session粘连或者session复制都不是特别好的方案。

Cookie的缺点

只能用于pc的浏览器中,而不能应用于移动端,因为移动端的浏览器通常会禁止cookie,而且移动端app用的更多的是Http客户端的形式。特别是如果不用Http协议那?token则适用于所有协议。

Token

Token的意思是“令牌”,是用户身份的验证方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由 token的前几位+盐哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。还可以把不变的参数也放进token,避免多次查库。token在服务器端并不保存,当服务器端收到token时,使用服务器端的特定的算法和只有服务器端知道的密钥,计算签名,如果相同则通过验证。token解决了上述的session和cookie的不足,减少了服务器端的开销,不用在服务器端存储会话标志,可以很好的支持分布式和支持多端登录。

其实token就相当于sessionId,为什么app喜欢用token,而不用sessionId呢?

app保管cookie不方便,不好维护cookie,因为cookie是浏览器的东西,app天生不支持cookie。浏览器也会出现关闭cookie的时候,这时,服务器会重写url,将sessionId放到url后面,如果app不打算用cookie,那么与浏览器关闭cookie无异,服务器将sessionId放到url后,app完全可以把sessionId拿出来,缓存到app数据库或者内存中,下次访问服务器就带上sessionId。

服务器默认的sessionId名称叫JSESSIONID,app说他们需要token,那么就把JSESSIONID换一个名称,就叫token,那么服务器和重写url返回sessionId一样,就把token放到url后面就可以了。实际token和JSESSIONID的值是一样的,换汤不换药,然后APP开发会觉得很高兴,他们终于拿到想要的token了。

app需要到登录服务器上登录,然后到应用服务器去获取数据,这里涉及到不同的服务器,那么不同服务器对应到该app客户端的sessionId肯定不一样,app不像浏览器那么强大,管理不同域下的sessionId都很方便,这时app肯定希望有一个统一的sessionId,那就是token,方便访问不同的服务器。

还有一个理由是,应用服务器需要校验当前app是否登录,于是要到登录服务器上去询问,询问时总要带个app的凭证吧,于是需要一个token,这个token是登录服务器给app的,应用服务器拿着这个token去询问登录服务器,登录服务器校验该token后,就会告知应用服务器当前app是否合法(登录过),然后应用服务器才能放心的把数据返回给app。

流程:

服务端收到请求,去验证用户名与密码。
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端。
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 持久化存储 里。
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token。
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。

Token的实现方法

a.用设备mac地址作为token,来标志一个用户

客户端:客户端在登录时获取设备的mac地址,将其作为参数传递到服务端

服务端:服务端接收到该参数后,用一个变量来接收,同时将其作为token保存在数据库,并将该token设置到session中。客户端每次请求的时候都要统一拦截,将客户端传递的token和服务器端session中的token进行对比,相同则登录成功,不同则拒绝。

此方式客户端和服务端统一了唯一的标识,并且保证每一个设备拥有唯一的标识。缺点是服务器端需要保存mac地址;优点是客户端无需重新登录,只要登录一次以后一直可以使用,对于超时的问题由服务端进行处理。

b.用sessionid作为token

客户端:客户端携带用户名和密码登录

服务端:接收到用户名和密码后进行校验,正确就将本地获取的sessionid作为token返回给客户端,客户端以后只需带上请求的数据即可。

此方式的优点是方便,不用存储数据,缺点就是当session过期时,客户端必须重新登录才能请求数据。

当然,对于一些保密性较高的应用,可以采取两种方式结合的方式,将设备mac地址与用户名密码同时作为token进行认证。

Token传输方法

①:防止表单重复提交(防止表单重复提交一般还是使用前后端都限制的方式,比如:在前端点击提交之后,将按钮置为灰色,不可再次点击,然后客户端和服务端的token各自独立存储,客户端存储在Cookie或者Form的隐藏域(放在Form隐藏域中的时候,需要每个表单)中,服务端存储在Session(单机系统中可以使用)或者其他缓存系统(分布式系统可以使用)中。)
②:用来作身份验证:单点登录(在公司的一个App登录后,其他的App也能自动登录),方法还是用服务器返回的token。

时效检测的token

客户端在第一次登录后,服务端会同时返回一个access token和refresh token,access token有失效期,相对较短,用于每次和服务端通信的身份校验。refresh token用于access token失效后换取新的access token时的校验,成功后返回新的access token和新的refresh token。所以refresh token传输次数少,并仅用于更新access token。

实现方法可以是服务器数据库+隐藏表单,响应头,session+cookie。

方法一:放在请求头中

方法二:隐藏表单

方法三:cookie

方法四:利用json等格式在数据中传输

Restful接口的认证与状态保持

Restful风格的服务器端可以利用很多框架实现比如EasyRest。Http本身是无状态的,我们通过session来保持用户状态,但是Rest风格也是要求无状态,那此时在访问这些接口时,如何保证用户访问认证及权限?

因为Rest风格接口不想我们平常用的Http协议,不需要利用session保持信息,比如购物车,用户浏览位置等。所以我们通常用token来进行访问控制即可。一般是用户登陆验证后,服务端返回一个token,然后客户端访问时候通过token来访问,至于token放那里那就是自己组织的问题了。为了安全性一般来说token根据每次登陆场景随机产生。

用 OAuth 只是完成了认证而已,之后每个独立的请求,你都需要将认证通过后颁发给你的 Token 携带在你的请求中(一般是header里),这个 Token 既可以用 Cookie header 传递,也可以用 access-token header 传递,而且你还可以自己定义一个 header 来保存,甚至你还可以将其作为 content 的内容来传递。用 Cookie header 有个好处,就是浏览器友好

无状态的交互,如果需要状态信息,那么就需要在每次请求中都携带状态信息。

如何防止重复请求攻击(RepeatAttack)和内容被篡改。发token的同时给每个token准备一个私钥也发给用户,client端的程序用私钥和URI和时间戳生成签名,每次请求除了表明身份的token之外还要有签名sign。你通过token拿到私钥,验证签名就行了。这样就防止了内容被篡改。

JWT

实施 Token 验证的方法挺多的,还有一些标准方法,比如 JWT,读作:jot ,表示:JSON Web Tokens 。JWT 标准的 Token 有三个部分:

  • header
  • payload
  • signature

中间用点分隔开,并且都会使用 Base64 编码。

其实这些都不安全,最安全的就是https。但是HTTPS效率低下。

在stackoverflow上Simone Carletti 提出了两种解决方案:

1. 使用一个基础HTTP认证,GitHub使用的就是这种方法,发送给指定URL的请求应该包含认证信息

http://api.example.com/resource/id
with basic authentication
username: token
password: the api key

这种方式的缺点十分明显,就是针对HTTP类型的请求,所有的信息都会暴露在外面,很容易就被破解掉,Github使用这种,是因为Github全站HTTPS了。

2. 将API Token作为一个查询参数传递过去,发送给指定URL的请求如下

http://api.example.com/resource/id?token=api_key

回答中提出了还有三种方法:

1. using an API key in the header (e.g. 'Authorization: Token MY_API_KEY') instead of as a url param. 在HTTP头里面存储token,而不是作为URL参数。这样更加安全。

2. 亚马逊的解决方案,加入了签名的TOKEN,即在客户端用密钥和id签名出一个token,然后传递token,到服务器端再次签名,这样由于密钥并没有在网络上传输,基本安全的,同时亚马逊还使用了时间参数防止重放攻击

——http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html

3. OAUTH认证,这种需要辅以用户账号系统,通常涉及到服务器,客户端和用户三方的认证,即用户对这次请求进行认证。

研究新浪的API访问控制,发现它使用的是AccessToken,有两种方式来使用该AccessToken:

1、API请求 URL 的后面加上一个AccessToken

2、Http头里面加一个字段AccessToken=xxx

这种AccessToken是写死在程序里面的,在每次请求的时候附带上,对于这种AccessToekn新浪那边有过期时间,过期之后就无法再使用了。相当于预制密钥在代码中。这种方法并不安全,所以利用这种方式使用的接口都是较为基础的接口,高级一点的接口需要使用Oauth 2.0进行二次认证的访问控制。

给出一个访问控制方法:

1、为每个应用颁发一个账号(user)和密码(password),相当于(公钥和私钥)。但是同样该账号密码也需要提前分发。

2、服务器后台存储该账号和密码(密文存储 MD5加密)

3、应用端在通过HTTP请求该接口的时候,需要在HTTP HEADER 附带下面几个字段

  • 时间date=unix时间戳
  • 签名sign=sign

该sign的生成算法是这样的

String sign = HTTPMETHOD(GET/POST)+ api_uri(API的访问URI)+date(即上面的UNIX时间戳)+length(发送body的数据长度)+password(后台颁发的密码)

sign = MD5(sign)

sign = user+":"+sign

4、后台服务器在接收到请求后

1、比对HTTP头中的时间戳,比对服务器时间,如果超过某个阈值,则拒绝访问,同时返回请校准你的应用时间。

2、如果没有超过时间阈值,则从sign中取出user然后在数据库中查找对应的password,然后同样根据上面的sign生成算法,来生成sign进行身份认证,认证成功则执行API,失败则返回认证失败。

JSON Web Token (JWT)

随着网络的扩展,Session有个问题,那就是具有状态性(Stateful),还有容易受到跨域请求伪造攻击(CSRF Attack)。

特别是分布式服务器做负载均衡的时候,session不可用。

而Token 本身是不带信息的且无状态的(Stateless),当服务器接收到Token时,会主动去对应使用者的本地存储的信息,接着就能够知道这个Token代表着哪个使用者,然后抓出相关的信息来使用

而使用JWT(目前最流行的跨域认证解决方案),你可以直接在JWT中存放数据,而不需要额外的本地存储数据库,也保护需要比对数据库。用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

 

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

IT服务常见的一些使用场景:

  1. 用户在web浏览器端登录系统,使用系统服务
  2. 用户在手机端(Android/iOS)登录系统,使用系统服务
  3. 用户使用开放接口登录系统,调用系统服务
  4. 用户在PC处理登录状态时通过手机扫码授权手机登录(使用得比较少)
  5. 用户在手机处理登录状态进通过手机扫码授权PC进行登录(比较常见)

通过对场景的细分,得到如下不同的认证token类别:

  1. 原始账号密码类别

    • 用户名和密码
    • API应用ID/KEY
  2. 会话ID类别

    • 浏览器端token
    • 移动端token
    • API应用token
  3. 接口调用类别

    • 接口访问token
  4. 身份授权类别

    • PC和移动端相互授权的token

你可能感兴趣的:(网络协议)