认证机制之JWT

认证机制之JWT

  • 常见的认证机制
    • HTTP BASIC AUTH
    • Session和cookie
    • JWT-Token认证
  • JWT简介
    • 什么是JWT
    • 特点
    • 优点
    • 缺点
    • 使用缺陷
    • 适用情景
    • 数据结构
      • JWT头(header)
        • 组成
        • 令牌类型
        • 散列算法
        • 转换字符串
      • 有效载荷(payload)
        • claims
          • registered claims
          • public claims
          • private claims
        • 注意
      • 签名
        • 目的
        • 参数
        • 生成签名公式
      • Base64URL算法
      • 注意
    • 原理
    • 大概的流程
    • 实现JWT—JJWT
        • 依赖
        • 创建token
        • 解析token
        • 设置过期时间
        • 自定义claims
  • 补充
    • 如何使token失效

常见的认证机制

HTTP BASIC AUTH

HTTP BASIC auth:每次请求API的时候,都会把用户名和密码通过restful API传给服务端。

可以实现一个无状态思想,即每次HTTP请求和以前都没有啥关系,只是获取目标URI,得到目标内容之后,这次连接就被杀死,没有任何痕迹。

缺点:通过http请求发送给服务端的时候,很有可能将我们的用户名密码直接暴漏给第三方客户端,风险特别大,因此生产环境下用这个方法很少。

Session和cookie

在服务端全局创建session对象,session对象保存着各种关键信息

向客户端发送一组sessionId,成为一个cookie对象保存在浏览器中。

认证时,cookie的数据会传入服务端与session进行匹配,进行数据认证。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t3YeeZAC-1576822693805)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\msohtmlclip1\01\clip_image002.jpg)]

实现的是一个有状态的思想,即该服务的实例可以将一部分数据随时进行备份,并且在创建一个新的有状态服务时,可以通过备份恢复这些数据,以达到数据持久化的目的。

缺点

  • 安全性。cookies的安全性不好,攻击者可以通过获取本地cookies进行欺骗或者利用cookies进行CSRF攻击。

  • 跨域问题。使用cookies时,在多个域名下,会存在跨域问题。

  • 有状态。session在一定的时间里,需要存放在服务端,因此当拥有大量用户时,也会大幅度降低服务端的性能。

  • 状态问题。当有多台机器时,如何共享session也会是一个问题,也就是说,用户第一个访问的时候是服务器A,而第二个请求被转发给了服务器B,那服务器B如何得知其状态。

  • 移动手机问题。现在的智能手机,包括安卓,原生不支持cookie,要使用cookie挺麻烦。

大致流程

  1. 用户向服务器发送用户名和密码
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个* session_id*,写入用户的* Cookie*。
  4. 用户随后的每一次请求,都会通过* Cookie*,将* session_id *传回服务器
  5. 服务器收到* session_id*,找到前期保存的数据,由此得知用户的身份。

JWT-Token认证

Token: 访问资源的凭据。

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。

JWT简介

什么是JWT

JSON Web Token(JWT)是一个基于json数据结构的,非常轻巧的规范

它允许我们使用jwt在用户和服务器之间传输安全可靠的信息。

可以通过数字签名进行验证和信任

虽然JWT可以加密以在各方之间提供保密,但只将专注于签名令牌。

签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。

当使用公钥/私钥对签署令牌时,签名还证明只有持有私钥的一方是签署私钥的一方。

特点

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

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

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

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

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

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

通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

优点

在分布式系统中,很好地解决了单点登录问题,很容易解决了session共享的问题。

那Token机制相对于Cookie机制的好处

  • 支持跨域访问:Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提 是传输的用户认证信息通过HTTP头传输.
  • 无状态:Token机制本质是校验, 他得到的会话状态完全来自于客户端, Token机制在服务端不需要存储session信息,因为 Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
  • 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript, HTML,图片等),而你的服务端只要提供API即可.
  • 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候,你可以进行Token生成调用即可.
  • 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等) 时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认 证机制就会简单得多。 CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
  • 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256 计算 的Token验证和解析要费时得多. 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要 为登录页面做特殊处理.
  • 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在 多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如: Firebase,Google, Microsoft)

相比Simple Web Tokens (SWT)(简单Web令牌)和Security Assertion Markup Language Tokens (SAML)(安全断言标记语言令牌);

  • JWT比SAML更简洁,在HTML和HTTP环境中传递更方便;
  • 在安全方面,SWT只能使用HMAC算法通过共享密钥对称签名。但是,JWT和SAML令牌可以使用X.509证书形式的公钥/私钥对进行签名。与签名JSON的简单性相比,使用XML数字签名可能会存在安全漏洞;
  • JSON解析成对象相比XML更流行、方便。

缺点

  1. 占带宽

正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多。

  1. 无论如何你需要操作数据库

在网站上使用 JWT,对于用户加载的几乎所有页面,都需要从缓存/数据库中加载用户信息,如果对于高流量的服务,你确定这个操作合适么?如果使用redis进行缓存,那么效率上也并不能比 session 更高效

  1. 无法在服务端注销,很难解决劫持问题

  2. 无法作废已经颁布的令牌

  3. 不易应对过期数据

  4. 性能问题

JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。

使用缺陷

用用进行交互的页面包含动态数据,需要频繁操作数据库时

JWT存储数据比cookie开销大

适用情景

从服务器到服务器或客户端到服务器(如:移动应用 APP 或单页面应用)的 API 服务

你的客户端需要通过 API 进行身份验证,并返回 JWT

客户端使用返回的 JWT 经过身份验证去请求其它的 API 服务

这些其它 API 服务通过客户端的 JWT 验证客户端是可信的,并且可以执行某些操作无需再次验证

授权:单点登录或 OpenID Connect 认证,实现一种通过第三方验证用户的方法。

因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态)

信息交换:JWT是在各个服务之间安全传输信息的好方法,由于使用标头和有效负载计算签名,还可以验证内容是否未被篡改。

数据结构

数据结构:一个很长的字符串,字符之间通过"."分隔符分为三个子串。各子串之间也没有换行符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a7wtjrwV-1576822693808)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\msohtmlclip1\01\clip_image004.jpg)]

JWT的三个部分如下。JWT头、有效载荷和签名,将它们写成一行

JWT头(header)

组成

alg:散列算法

typ:令牌类型

{

​      alg: 签名使用的算法, 默认为HMAC SHA256(HS256)

typ: 令牌的类型,JWT令牌统一写为JWT

}

令牌类型

只能是JWT

散列算法

JWS 算法名称 描述
HS256 HMAC256 HMAC with SHA-256
HS384 HMAC384 HMAC with SHA-384
HS512 HMAC512 HMAC with SHA-512
RS256 RSA256 RSASSA-PKCS1-v1_5 with SHA-256
RS384 RSA384 RSASSA-PKCS1-v1_5 with SHA-384
RS512 RSA512 RSASSA-PKCS1-v1_5 with SHA-512
ES256 ECDSA256 ECDSA with curve P-256 and SHA-256
ES384 ECDSA384 ECDSA with curve P-384 and SHA-384
ES512 ECDSA512 ECDSA with curve P-521 and SHA-512

转换字符串

使用Base64Url算法将JSON转换为字符串,形成JWT的第一部分

有效载荷(payload)

载荷就是存放有效信息的地方

claims

claims是关于实体(常用的是用户信息)和其他数据的声明

claims有三种类型: registered, public,private claims。

registered claims

这些是一组预定义的claims,非强制性的,但是推荐使用

  • iss(发行人)
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
public claims

自定义claims,注意不要和JWT注册表中属性冲突

一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

private claims

自定义的claims

用于在同意使用这些claims的各方之间共享信息

它们既不是Registered claims,也不是Public claims。

使用Base64Url算法将JSON转换为字符串,形成JWT的第一部分

注意

对于签名令牌,此信息虽然可以防止篡改,但任何人都可以读取。除非加密,否则不要将敏感信息放入到Payload或Header元素中。
自定义claim和标准claim的区别

JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

使用方法

JWT.create().withHeader(map) // header

.withClaim(“iss”, “Service”) // payload

.withClaim(“aud”, “APP”)

.withIssuedAt(iatDate) // sign time

.withExpiresAt(expiresDate) // expire time

签名

目的

确保数据不会被篡改

参数

编码的Header,编码的Payload,秘钥,Header中指定的算法

密钥:仅保存在服务器中,不向用户公开

在使用私钥签名令牌的情况下,它还可以验证JWT的请求方是否是它所声明的请求方。

生成签名公式

HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),

secret)

输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,与SAML等基于XML的标准相比更加紧凑。

Base64URL算法

JWT头和有效载荷序列化的算法都用到了Base64URL。

该算法和常见Base64算法类似,稍有差别。

作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。

Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义

Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。

注意

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

原理

服务器认证以后,生成一个 JSON 对象,发回给用户

用户将信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。

客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。

此后,客户端将在与服务器交互中都会带JWT。

服务器完全只靠这个对象认定用户身份。

为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名

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

如果将它存储在Cookie中,就可以自动发送,但是不会跨域

一般是将它放入HTTP请求的Header Authorization字段中。Authorization: Bearer

当跨域时,也可以将JWT被放置于POST请求的数据主体中。

JWT 的内容(内部的 JSON 数据)通常是不加密的。这意味着,即使没有密钥,也可以查看 JWT 内的数据。JWT 默认并不会加密你的数据,它只是帮助你验证是你信任的一方创建了它。

大概的流程

  1. 客户端使用用户名跟密码请求登录

  2. 服务端收到请求,去验证用户名与密码

  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端

  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里

  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token

  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

Token机制,我认为其本质思想就是将session中的信息简化很多,当作cookie用,也就是客户端的“session”。

实现JWT—JJWT

为了更友好在JVM上使用JWT

是基本于JWT, JWS, JWE, JWK框架的java实现。

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

官方文档:https://github.com/jwtk/jjwt

依赖

Maven



io.jsonwebtoken

jjwt

0.9.0


Gradle:

dependencies {

compile 'io.jsonwebtoken:jjwt:0.9.0'

}

创建token

JwtBuilder builder= Jwts.builder()

.setId("xxx") //设置唯一编号

.setSubject("xxx")//设置主题 可以是JSON数据

.setIssuedAt(new Date())//设置签发日期

.signWith(SignatureAlgorithm.HS256,"hahaha");//设置签名 使用HS256算法,并设置SecretKey(字符串)

//构建 并返回一个字符串

System.out.println( builder.compact() );

再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

解析token

在web应用中创建token是由服务端进行然后发给客户端

客户端在下次向服务端发送请求时需要携带这个token。

服务端接到这个token 应该解析出token中的信息,根据这些信息查询数据库返回相应的结果。

String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXXreFU_u3Y";

Claims claims = Jwts.parser().setSigningKey("hahaha").parseClaimsJws(compactJwt).getBody();

System.out.println(claims);

运行打印效果:

试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token.

设置过期时间

(1)创建token 并设置过期时间

long now=System.currentTimeMillis();

long exp=now+1000*30;//30秒过期

JwtBuilder jwtBuilder = Jwts.builder().setId( "xxx" )

.setSubject( "xxx" )

.setIssuedAt( new Date() )//签发时间

.setExpiration( new Date( exp ) )//过期时间

.signWith( SignatureAlgorithm.HS256, "hahaha" );

String token = jwtBuilder.compact();

System.out.println(token);

(2)解析TOKEN

String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI";

Claims claims = Jwts.parser().setSigningKey("hahaha").parseClaimsJws(compactJwt).getBody();

System.out.println(claims);

当前时间超过过期时间,则会报错。

自定义claims

想存储更多的信息可以定义自定义claims。

long now=System.currentTimeMillis();

long exp=now+1000*30;//30秒过期

JwtBuilder jwtBuilder = Jwts.builder().setId( "xxx" )

.setSubject( "xxx" )

.setIssuedAt( new Date() )//签发时间

.setExpiration( new Date( exp ) )//过期时间

.claim( "roles","admin" )//自定义的claim

.signWith( SignatureAlgorithm.HS256, "hahaha" );

String token = jwtBuilder.compact();

System.out.println(token);

解析TOKEN:

String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIyNTM3NTQsImV4cCI6MTU2MjI1Mzc4Mywicm9sZXMiOiJhZG1pbiJ9.CY6CMembCi3mAkBHS3ivzB5w9uvtZim1HkizRu2gWaI";

Claims claims = Jwts.parser().setSigningKey( "hahaha" ).parseClaimsJws( token ).getBody();

System.out.println(claims);

System.out.println(claims.get( "roles" ));

注意,jjwt不支持jdk11,0.9.1以后的jjwt必须实现signWith()方法才能实现

补充

如何使token失效

只能是客户端把token清理掉,或者token自己过期

服务端手动增加清楚token的逻辑

你可能感兴趣的:(认证机制之JWT)