Java Web Token

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

JWT组成

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

荷载

{
"iss": "John Wu JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"from_user": "B",
"target_user": "A"
}

这里面的前五个字段都是由JWT的标准所定义的。

  • iss: 该JWT的签发者
  • sub: 该JWT所面向的用户
  • aud: 接收该JWT的一方
  • exp(expires): 什么时候过期,这里是一个Unix时间戳
  • iat(issued at): 在什么时候签发的

这些定义都可以在标准中找到。

将上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。

eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

头部

JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

{

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

在这里,我们说明了这是一个JWT,并且我们所用的签名算法(后面会提到)是HS256算法。(算法根据实际情况而变)

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

签名

签名就是将荷载和头部编码用.号连接在一起就形成了

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容

rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

签名的目的

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小(有可能比我成世界首富的概率还小)。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。

所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。

服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用alg字段指明了我们的加密算法了。

如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

我们可以看到,JWT适合用于向Web应用传递一些非敏感信息。例如在上面提到的完成加好友的操作,还有诸如下订单的操作等等。

其实JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

Spring使用JWT

Maven配置方式

<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.2.0</version>
</dependency>

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

使用方法

选择算法

算法定义了一个令牌是如何被签名和验证的。它可以用HMAC算法的原始值来实例化,也可以在RSA和ECDSA算法的情况下对密钥对或密钥提供程序进行实例化。创建后,该实例可用于令牌签名和验证操作。

在使用RSA或ECDSA算法时,只需要签署JWTs,就可以通过传递null值来避免指定公钥。当您需要验证JWTs时,也可以使用私钥进行操作

使用静态的字符密文或者key来获取算法器:

//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");//RSA
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);

使用一个key提供者来获取算法:
通过使用KeyProvider,您可以在运行时更改密钥,用于验证令牌签名或为RSA或ECDSA算法签署一个新的令牌。这是通过实现RSAKeyProvider或ECDSAKeyProvider方法实现的:
- getPublicKeyById(String kid): 它在令牌签名验证中调用,它应该返回用于验证令牌的密钥。如果使用了关键的轮换,例如JWK,它可以使用id来获取正确的轮换键(或者只是一直返回相同的键)。
- getPrivateKey(): 在令牌签名期间调用它,它应该返回用于签署JWT的密钥。
- getPrivateKeyId():在令牌签名期间调用它,它应该返回标识由getPrivateKey()返回的键的id的id。这个值比JWTCreator.Builder和keyid(String)方法中的值更受欢迎。如果您不需要设置孩子的值,就避免使用KeyProvider实例化算法。

创建JWT

try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create().withIssuer("auth0").sign(algorithm);
} catch (UnsupportedEncodingException exception){//UTF-8 encoding not supported
} catch (JWTCreationException exception){//Invalid Signing configuration / Couldn't convert Claims.
}

如果Claim不能转换为JSON,或者在签名过程中使用的密钥无效,那么将会抛出JWTCreationException异常。

验证令牌

首先需要通过调用jwt.require()和传递算法实例来创建一个JWTVerifier实例。如果您要求令牌具有特定的Claim值,请使用构建器来定义它们。方法build()返回的实例是可重用的,因此您可以定义一次,并使用它来验证不同的标记。最后调用verifier.verify()来验证token

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {Algorithm algorithm = Algorithm.HMAC256("secret");JWTVerifier verifier = JWT.require(algorithm).withIssuer("auth0").build(); //Reusable verifier instanceDecodedJWT jwt = verifier.verify(token);
} catch (UnsupportedEncodingException exception){//UTF-8 encoding not supported
} catch (JWTVerificationException exception){//Invalid signature/claims
}

时间验证

当验证一个令牌时,时间验证会自动发生,导致在值无效时抛出一个JWTVerificationException。如果前面的任何一个字段都丢失了,那么在这个验证中就不会考虑这些字段。

要指定令牌仍然被认为有效的余地窗口,在JWTVerifier builder中使用accept回旋()方法,并传递一个正值的秒值。这适用于上面列出的每一项。

JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) // 1 sec for nbf, iat and exp
.build();

您还可以为给定的日期声明指定一个自定义值,并为该声明覆盖缺省值。

JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1)   //1 sec for nbf and iat
.acceptExpiresAt(5)   //5 secs for exp
.build();

信息解析

Algorithm (“alg”)

返回jwt的算法值或,如果没有定义则返回null

String algorithm = jwt.getAlgorithm();

如果您需要在您的lib/app中测试此行为,将验证实例转换为basever可视化,以获得verific.build()方法的可见性,该方法可以接受定制的时钟。例如:

BaseVerification verification = (BaseVerification) JWT.require(algorithm)
.acceptLeeway(1)
.acceptExpiresAt(5);
Clock clock = new CustomClock(); //Must implement Clock interface
JWTVerifier verifier = verification.build(clock);

Type (“typ”)

返回jwt的类型值,如果没有定义则返回null(多数情况类型值为jwt)

String type = jwt.getType();

Content Type (“cty”)

返回内容的类型,如果没有定义则返回null

String contentType = jwt.getContentType();

Key Id (“kid”)

返回key的id值,如果没有定义则返回null

String keyId = jwt.getKeyId();

自定义字段

在令牌的头部中定义的附加声明可以通过调用getHeaderClaim() 获取,即使无法找到,也会返回。您可以通过调用claim.isNull()来检查声明的值是否为null。

Claim claim = jwt.getHeaderClaim("owner");

当使用jwt.create()创建一个令牌时,您可以通过调用withHeader()来指定头声明,并同时传递声明的映射。

Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create().withHeader(headerClaims).sign(algorithm);

提示:在签名过程之后,alg和typ值将始终包含在Header中。

JWT的负载(Payload)声明

Issuer ("iss")

返回签发者的名称值,如果没有在负载中定义则返回null

String issuer = jwt.getIssuer();
Subject ("sub")

返回jwt所面向的用户的值,如果没有在负载中定义则返回null

String subject = jwt.getSubject();
Audience ("aud")

返回该jwt由谁接收,如果没有在负载中定义则返回null

List<String> audience = jwt.getAudience();
Expiration Time ("exp")

返回该jwt的过期时间,如果在负载中没有定义则返回null

Date expiresAt = jwt.getExpiresAt();
Not Before ("nbf")

Returns the Not Before value or null if it’s not defined in the Payload.

Date notBefore = jwt.getNotBefore();
Issued At ("iat")

返回在什么时候签发的,如果在负载中没有定义则返回null

Date issuedAt = jwt.getIssuedAt();
JWT ID ("jti")

返回该jwt的唯一标志,如果在负载中没有定义则返回null

String id = jwt.getId();

自定义声明

在令牌有效负载中定义的附加声明可以通过调用getClaims()或 getClaim()和传递声明名来获得。即使无法找到声明,也将会有返回值。您可以通过调用claim.isNull()来检查声明的值是否为null。

Map<String, Claim> claims = jwt.getClaims();    //Key is the Claim name
Claim claim = claims.get("isAdmin");

或者:

Claim claim = jwt.getClaim("isAdmin");

当使用jwt.create()创建一个令牌时,您可以通过调用withClaim()来指定自定义声明,并同时传递名称和值。

String token = JWT.create().withClaim("name", 123).withArrayClaim("array", new Integer[]{1, 2, 3}).sign(algorithm);

您还可以通过调用withClaim()来验证jwt.require()的自定义声明,并传递该名称和所需的值。

JWTVerifier verifier = JWT.require(algorithm).withClaim("name", 123).withArrayClaim("array", 1, 2, 3).build();
DecodedJWT jwt = verifier.verify("my.jwt.token");

实例

package course.utils;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.collect.Maps;
import course.pojo.User;import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;public class JwtUtils {//创建tokenpublic static String creatToken(User user) throws IllegalArgumentException, UnsupportedEncodingException{Algorithm algorithm = Algorithm.HMAC256("secret");String username = user.getUsername();Map<String, Object> map = Maps.newHashMap();map.put("alg", "HS256");map.put("typ", "JWT");String token = JWT.create().withHeader(map).withClaim("username", username).withExpiresAt(new Date(System.currentTimeMillis()+360000)).sign(algorithm);return token;}//验证jwtpublic static DecodedJWT verifyJwt(String token){DecodedJWT decodedJWT = null;try{Algorithm algorithm = Algorithm.HMAC256("secret");JWTVerifier jwtVerifier = JWT.require(algorithm).build();decodedJWT = jwtVerifier.verify(token);}catch(IllegalArgumentException e){e.printStackTrace();}catch (UnsupportedEncodingException e){e.printStackTrace();}catch(JWTVerificationException e) {e.printStackTrace();}return decodedJWT;}public static void main(String[] args) throws UnsupportedEncodingException{
//        String username = "root";
//        Integer id =1;
//        System.out.println(creatToken(username,id));
//        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDgxMzgxNDAsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJyb290In0.OeRdHJZKmxFBqIN-A-uSNQK8JyKdzX-wcFR883oMqFA";
//        System.out.println(verifyJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDgxMzgxNDAsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJyb290In0.OeRdHJZKmxFBqIN-A-uSNQK8JyKdzX-wcFR883oMqFA"));
//        System.out.println(verifyJwt(token).getClaims().get("username").asString());}
}

Spring框架整合Java Web Token相关推荐

  1. Spring框架:Java领域的瑰宝

    前言   Spring是Java领域中最流行.最成熟的开源框架之一,它为企业级应用提供了一个全方位的解决方案.本文将基于官方文档,介绍Spring框架的核心概念和主要功能模块,并详细说明各种配置方式及 ...

  2. Java Web Token 之 JJWT 使用

    文章目录 Java Web Token 之 JJWT 使用 1. JJWT 简介 2. JJWT 引入 2.1 Maven依赖 3. 快速入门 3.1 构建 JWT 3.2 解析 JWT 4. JWT ...

  3. 【Java】MyBatis与Spring框架整合(一)

    本文将利用 Spring 对 MyBatis 进行整合,在对组件实现解耦的同时,还能使 MyBatis 框架的使用变得更加方便和简单. 整合思路 作为 Bean 容器,Spring 框架提供了 IoC ...

  4. struts2+ibatis+spring框架整合(二)

    MyBatis3.1.1+Spring3.1.2+Struts2.3.4.1框架整合 先来看目录结构 来看配置文件 applicationContext.xml <?xml version=&q ...

  5. 基于Stripes框架进行Java Web开发

    Mark Eagle是美国乔治亚州亚特兰大市MATRIX Resources有限公司的一位资深软件工程师,拥有Sun公司的SCP和SCWCD认证.Mark本人非常喜欢使用开源软件进行软件开发,并且多次 ...

  6. spring boot web项目_阿里技术专家带你使用Spring框架快速搭建Web工程项目

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 第一时间与你相约 每日英文 We all have a past. It's how you deal with it. 每个人 ...

  7. maven + spring mvc 创建Java web项目

    本篇文章,我们会教你通过maven和spring mvc创建一个Java web项目.关于maven如何创建普通的Java项目,上一篇文章已经作了说明. 开发环境: 1.jdk 1.7 2.Maven ...

  8. Spring框架整合JUnit单元测试

    1. 为了简化了JUnit的测试,使用Spring框架也可以整合测试 2. 具体步骤* 要求:必须先有JUnit的环境(即已经导入了JUnit4的开发环境)!!* 步骤一:在程序中引入:spring- ...

  9. EasyUI、Struts2、Hibernate、spring 框架整合

    经历了四个月的学习,中间过程曲折离奇,好在坚持下来了,也到了最后框架的整合中间过程也只有自己能体会了. 接下来开始说一下整合中的问题和技巧: 1,  jar包导入 c3p0(2个).jdbc(1个). ...

最新文章

  1. python、numpy,keras,tensorflow等函数用法积累(持续更新)
  2. py-faster-rcnn + ZF 实现自己的数据训练与检测(二)
  3. lambda中使用filter过滤
  4. 同一进程中的线程究竟共享哪些资源
  5. vue中input多选_vue实现下拉多选vue实现多选下拉框
  6. pytorch FC_regression
  7. Spring----AOP的术语
  8. 2013程序员考证下午题练习
  9. python脚本自动填调查问卷
  10. 优酷、腾讯视频播放器接口参数说明
  11. 操作系统--多线程进阶(上)
  12. Android环境搭建
  13. 文章收录技巧(怎么提升网站伪原创文章的收录)
  14. 乔纳森·艾维:iPhoneX准备了五年,苹果仍在不断创新
  15. android项目小说阅读开发背景颜色,Android 小说阅读护眼模式
  16. 被世界吉尼斯总部评为世界上最大的瀑布群
  17. 内地朋友对香港银行开户的需求越发增多!
  18. onnx 模型推理示例-Python 实现 |【onnx 模型推理】
  19. 鸿蒙操作系统细节曝光,华为鸿蒙系统细节曝光 UI 重绘动画多 / 系统简介更流畅...
  20. echars世界地图,显示指定的国家散点图

热门文章

  1. java制造业MES企业生产管理系统,ERP系统源码 MES制造执行系统
  2. cps配置之京东联盟篇
  3. Tapdata PDK 生态共建计划启动 MongoDB、Doris、OceanBase、PolarDB等十余家厂商首批加入
  4. jupyter notebook占用内存空间过大打不开的解决办法(包括但不限于爬虫造成的)
  5. 微机化远动系统与计算机网络,计算机网络在电力系统中的应用-《电力系统远动及调度自动化》思考题题解...
  6. 巴伦(来自微博射频网)
  7. Word快捷键及实用功能
  8. Subroutine 子程序 Perl 第四章
  9. TiDB---PCTA认证
  10. SQL 关系除法和AS别名的坑