JWT学习&JWT和SpringBoot的整合

    科技2024-05-30  78

    文章目录

    1.常见的认证机制1.1HTTP BASIC Auth(无状态)1.2 Cookie Auth (有状态)1.3 OAuth1.4 Token Auth 2基于JWT的Token认证机制实现2.1 什么是JWT2.2 JWT组成 3JAVA的JJWT实现JWT3.1 JJWT快速入门3.2 token的创建3.3 token的解析3.4 设置token的过期时间自定义claims自定义JWT工具类

    1.常见的认证机制

    1.1HTTP BASIC Auth(无状态)

    HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和 password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供 用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被 使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth 无状态 就是服务器不存储你的状态

    1.2 Cookie Auth (有状态)

    Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端 的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的 session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删 除。但可以通过修改cookie 的expire time使cookie在一定时间内有效; 缺点: CSRF就是靠Cookie来攻击的 Cookie他不安全 依赖浏览器,移植性不好

    1.3 OAuth

    OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在 某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和 密码提供给第三方应用。

    OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提 供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时 段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这 样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信 息,而非所有内容 下面是OAuth2.0的流程:

    1.4 Token Auth

    使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是 这样的:

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

    Token Auth的优点 Token机制相对于Cookie机制又有什么好处呢?

    支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提 是传输的用户认证信息通过HTTP头传输. 无状态(也称:服务端可扩展行):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 做功能测试的时候,不再需要 为登录页面做特殊处理.

    2基于JWT的Token认证机制实现

    2.1 什么是JWT

    JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用 户和服务器之间传递安全可靠的信息。

    2.2 JWT组成

    一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

    头部(Header) 头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以 被表示成一个JSON对象。主要声明用什么加密,用什么编码,

    {“typ”:“JWT”,“alg”:“HS256”}

    在头部指明了签名算法是HS256算法。 我们进行BASE64编 码http://base64.xpcha.com/,编码后的字符串如下:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

    小知识:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2 的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24 个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中 提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的 完成基于 BASE64 的编码和解码

    载荷(playload) 载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包 含三个部分

    (1)标准中注册的声明(建议但不强制使用)

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

    (2)公共的声明

    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息. 但不建议添加敏感信息,因为该部分在客户端可解密. (3)私有的声明

    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64 是对称解密的,意味着该部分信息可以归类为明文信息。 这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的 claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在 拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而 private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

    定义一个payload:

    {"sub":"1234567890","name":"John Doe","admin":true}

    然后将其进行base64编码,得到Jwt的第二部分。

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

    签证(signature) jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    header (base64后的) payload (base64后的) secret

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符 串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第 三部分。

    TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7Hg Q

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

    3JAVA的JJWT实现JWT

    3.1 JJWT快速入门

    3.2 token的创建

    (1)创建maven工程,引入依赖

    <dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency> </dependencies>

    (2)创建类CreateJwtTest,用于生成token

    public class CreateToken { public static void main(String[] args) { JwtBuilder builder = Jwts.builder() .setId("888").setSubject("小白").setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "azwnini"); System.out.println(builder.compact()); } }

    3.3 token的解析

    创建ParseJwtTest

    public class ParseToken { public static void main(String[] args) { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE2MDIwNTc0NTF9.wT5tZPKCNu1Bqmulp2tlkvUIaMvBdvxF_ML43oDfXnc"; Claims claims = Jwts.parser().setSigningKey("azwnini").parseClaimsJws(token).getBody(); System.out.println("id:" + claims.getId()); System.out.println("subject:" + claims.getSubject()); System.out.println("IssuedAt:" + claims.getIssuedAt()); } }

    成功解析

    3.4 设置token的过期时间

    有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个 过期时间。 创建CreateJwtTest

    public class CreateJwtTest { public static void main(String[] args) { long now = System.currentTimeMillis(); long exp = now + 1000 * 60; JwtBuilder builder = Jwts.builder() .setId("888").setSubject("小白").setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "azwnini") .setExpiration(new Date(exp)); System.out.println(builder.compact()); } }

    核心就是增加一个 : .setExpiration(new Date(exp));

    我们将他生成的Token放到代码中一分钟后去运行,就会出现一个过期异常,逻辑通

    自定义claims

    刚才我们在里面增加的参数 id subject IssuedAt,都是他里面自己的参数,如果我们要定制我们自己的参数信息,就需要自定义claims

    创建 CreateJwtTest3

    public class CreateJwtTest3 { public static void main(String[] args) { long now = System.currentTimeMillis(); long exp = now + 1000 * 60; JwtBuilder builder = Jwts.builder() .setId("888").setSubject("小白").setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "azwnini") .setExpiration(new Date(exp)) .claim("diyTag1","哈哈哈哈") .claim("diyTag2","555555"); System.out.println(builder.compact()); } }

    修改ParseJwtTest

    public class ParseToken { public static void main(String[] args) { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE2MDIwNTg3NjEsImV4cCI6MTYwMjA1ODgyMSwiZGl5VGFnMSI6IuWTiOWTiOWTiOWTiCIsImRpeVRhZzIiOiI1NTU1NTUifQ.ywpaiyjHEvfI7IMUBqEl2KC6aT0L3MB3IUMCsSmV5Kw"; Claims claims = Jwts.parser().setSigningKey("azwnini").parseClaimsJws(token).getBody(); System.out.println("id:" + claims.getId()); System.out.println("subject:" + claims.getSubject()); System.out.println("IssuedAt:" + claims.getIssuedAt()); System.out.println("下面是我自定义的标签信息咯================"); System.out.println("diyTag1: " + claims.get("diyTag1")); System.out.println("diyTag2: " + claims.get("diyTag2")); } }

    自定义JWT工具类

    TODO

    Processed: 0.010, SQL: 8