JWT

JSON Web Token(JWT)

view-on.10b22186

JSON Web Token(JWT)

JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于将信息作为 JSON 对象在各方之间安全地传输。此信息是经过数字签名的,因此可以验证和信任。JWT可以使用密钥(使用 HMAC 算法)或使用 RSAECDSA 的公钥/私钥对对 JWT 进行签名。

尽管 JWT 可以加密以在各方之间提供机密性,但我们将重点介绍签名令牌。签名令牌可以验证其中包含的声明的完整性 ,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对令牌进行签名时,签名还会证明只有持有私钥的一方是签署私钥的一方。

何时应使用 JWT?

以下是 JSON Web 令牌有用的一些情况:

授权 :这是使用 JWT 的最常见场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销小并且能够轻松地跨不同域使用。

信息交换 :JSON Web 令牌是在各方之间安全地传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确保发件人是他们所声称的身份。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。

为什么要使用

基于传统的Session认证

1、认证方式

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

2、认证流程

image-20250428002103463

3、暴露问题
  1. 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
  2. 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
  3. 因为是基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
  4. 在前后端分离系统中就更加痛苦:如下图所示 也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session每次携带sessionid到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制方便集群应用。

image-20250428011024356

传统认证方式:

将数据存储在session中,tomcat默认会存储session大约30分钟

第一次访问之后就会要求浏览器添加sessionidcookie,方便下次服务器查找用户会话(查找对于多用户压力大)

java
@GetMapping("/session")
public String session(HttpServletRequest request) {
    log.info((String) request.getSession().getAttribute("username"));
    log.info(request.getSession().getId());
    request.getSession().setAttribute("username", "admin");
    request.getSession().setAttribute("password", "123456");
    return "Hello";
}

image-20250428010623470

可以通过设置cookie让客户端每次携带账户密码,这样session失效也可以再次完成验证

在我看来jwt只是后端往前端传用户数据时的一种比较好的解决方案,特别是前后端分离项目中,避免了使用session,通过把用户数据(不包括敏感数据)放在请求头header中传给后端,既可以认证又可以授权(把角色放入token中),同时又由于签名的加密,无法伪造token,保证了安全性。但是jwt说到底只是生成token的一种机制,感觉还是要结合shiro,security一起使用,否则感觉独木难支啊。

另外,前后端分离场景下,token传给前端后,浏览器是不是要把token存在localStorage中,然后每次发请求就取出来塞到header中,这样做感觉也挺麻烦啊。希望陈哥能够解惑

基于JWT认证

image-20250428011753993

服务端认证,但是服务端不再存储

1、认证流程
  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被探

  • 后端核对用户名和密码成功后,将用户的 id 等其他信息作为JWTPayload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同111.zzz.xxx的字符串。

  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStoragesessionStorage上,退出登录时前端删除保存的JWT即可。

  • 前端在每次请求时将JWT放入HTTPHeader中的Authorization位。(解决XXS和XSRF问题)

  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

  • 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果

2、jwt优势
  • 简洁(Compact):可以通过URL,POST参数或者在HTTPheader发送,因为数据量小,传输速度也很快

  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持

  • 不需要在服务端保存会话信息,特别适用于分布式微服务。

什么是 JSON Web 令牌结构?

在其紧凑形式中,JSON Web 令牌由三个部分组成,由点 (.) 分隔,它们是:

  • Header 标头
  • Payload 有效载荷
  • Signature 签名 因此,JWT 通常如下所示:
xxxxx.yyyyy.zzzzz
header.payload.signature
{}.{}.{}
三个都是对象

让我们分解不同的部分。

Header 页眉

标头通常由两部分组成:令牌的类型(JWT)和正在使用的签名算法,例如 HMAC SHA256 或 RSA。

例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后,此 JSON 经过 Base64Url 编码以形成 JWT 的第一部分。

Payload 有效载荷

令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明: 已注册公共私有声明。

  • 已注册的声明 :这些是一组预定义的声明,不是强制性的,但建议使用,以提供一组有用的、可互作的声明。其中一些是:iss(颁发者)、exp(过期时间)、sub(主题)、aud(受众 )等。

Notice that the claim names are only three characters long, as JWT is meant to be compact. 请注意,声明名称只有三个字符长,因为 JWT 是紧凑的。

Public claims:这些声明可以由使用 JWT 的用户随意定义。但为避免冲突,应在 IANA JSON Web 令牌注册表中定义它们,或将其定义为包含抗冲突命名空间的 URI。

私有声明 :这些是自定义声明,用于在同意使用它们的各方之间共享信息,既不是注册声明,也不是公开声明。

一个示例有效负载可以是:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
//能用到的信息都放进去

然后,对有效负载进行 Base64Url 编码,以形成 JSON Web 令牌的第二部分。

请注意,对于签名令牌,此信息虽然可以防止篡改,但任何人都可以读取。除非 JWT 已加密,否则不要将机密信息放在 JWT 的 payload 或 header 元素中。

Signature 签名

前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。Signature需要使用编码后的header和payload以及我们提供的一个密钥,然后使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被募改过

要创建签名部分,您必须获取编码的标头、编码的有效负载、密钥、标头中指定的算法,并对其进行签名。

例如,如果您想使用 HMAC SHA256 算法,将按以下方式创建签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
  
secret就是签名,永远不能给别人的

签名用于验证消息在整个过程中没有被更改,并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发件人是否是它所声称的身份。

把所有的东西放在一起

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

下面显示了一个 JWT,该 JWT 对之前的标头和有效负载进行了编码,并使用密钥进行了签名。

Encoded JWT

如果您想使用 JWT 并将这些概念付诸实践,则可以使用 jwt.io Debugger 来解码、验证和生成 JWT。

使用JWT

1、引入依赖

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

2、生成token

java
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,90);
String tocken= JWT.create()
        .withClaim("username","张三")//设置用户信息
        .withExpiresAt(instance.getTime())//设置过期时间
        .sign(Algorithm.HMAC256("token!Q2W#RD$"));//设置签名 保密 复杂
log.info(tocken);

生成结果:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDU3NzYzNjgsInVzZXJuYW1lIjoi5byg5LiJIn0.rmIl_6tJs8b15MF0OtPVUlv8Bti9EQ6t9jbesY9Kc_8

3、根据令牌和签名解析数据

//基于签名构建JWT验签对象
JWTVerifier jwtVerifier=JWT.require(Alqorithm.HMAc256("token!2W#EsRW")).build()
//验证通过不会有异常,获得一个解码对象,可以获得原来塞进去的数据
DecodedJWT decodedJwT = jwtVerifier.verify(token);
System.out.println("用户名:"+decodedJwT.getClaim("username").asString());
System.out.println("过期时间:"+decodedJwT.getExpiresAt());

4、常见异常信息

SignatureVerificationException签名不一致异常
TokenExpiredException:令牌过期异常
AlgorithmMismatchException:算法不匹配异常
InvalidclaimException:失效的payload异常

整合SpringBoot

1、创建工具类JWTUtils简化操作

2、创建拦截器统一管理部分url的权限

image-20250428022600622

拦截与放行

image-20250428022617118

JSON Web 令牌如何工作?

在身份验证中,当用户使用其凭证成功登录时,将返回 JSON Web 令牌。由于令牌是凭据,因此必须非常小心以防止出现安全问题。通常,令牌的保留时间不应超过所需时间。

由于缺乏安全性,您也不应将敏感会话数据存储在浏览器存储中

每当用户想要访问受保护的路由或资源时,用户代理都应该发送 JWT,通常在 Authorization 标头中使用 Bearer 架构。标头的内容应如下所示:

Authorization: Bearer <token>

在某些情况下,这可以是无状态授权机制。服务器的受保护路由将在 Authorization 标头中检查有效的 JWT,如果存在,则允许用户访问受保护的资源。如果 JWT 包含必要的数据,则可能会减少查询数据库以进行某些作的需要,但情况可能并非总是如此。

请注意,如果您通过 HTTP 标头发送 JWT 令牌,则应尽量防止它们变得太大。某些服务器不接受超过 8 KB 的标头。如果您尝试在 JWT 令牌中嵌入太多信息,例如包含所有用户的权限,则可能需要替代解决方案,例如 Auth0 Fine-Grained Authorization

如果令牌在 Authorization 标头中发送,则跨域资源共享 (CORS) 不会成为问题,因为它不使用 Cookie。

下图显示了如何获取 JWT 并用于访问 API 或资源:

client-credentials-grant

  1. 应用程序或客户端向授权服务器请求授权。这是通过不同的授权流之一执行的。例如,典型的符合 OpenID Connect 的 Web 应用程序将使用授权代码流通过 /oauth/authorize 终端节点。
  2. 授予授权后,授权服务器将向应用程序返回访问令牌。
  3. 应用程序使用访问令牌访问受保护的资源(如 API)。

请注意,使用签名令牌时,令牌中包含的所有信息都会公开给用户或其他方,即使他们无法更改它。这意味着您不应将机密信息放在令牌中。

为什么我们应该使用 JWT

我们来谈谈 JSON Web 令牌 (JWT)简单 Web 令牌 (SWT) 和安全断言标记语言令牌 (SAML) 相比的优势。

由于 JSON 不如 XML 详细,因此在编码时,其大小也更小,从而使 JWT 比 SAML 更紧凑。这使得 JWT 成为在 HTML 和 HTTP 环境中传递的不错选择。

在安全性方面,SWT 只能由使用 HMAC 算法的共享密钥对称签名。但是,JWT 和 SAML 令牌可以使用 X.509 证书形式的公钥/私钥对进行签名。与对 JSON 进行签名的简单性相比,使用 XML 数字签名对 XML 进行签名而不引入模糊的安全漏洞非常困难。

JSON 解析器在大多数编程语言中都很常见,因为它们直接映射到对象。相反,XML 没有自然的文档到对象的映射。这使得使用 JWT 比使用 SAML 断言更容易。

关于使用情况,JWT 用于 Internet 规模。这突出了在多个平台(尤其是移动平台)上对 JSON Web 令牌进行客户端处理的便利性。

comparing-jwt-vs-saml2

编码的 JWT 和编码的 SAML 的长度比较

如果您想了解有关 JSON Web 令牌的更多信息,甚至开始使用它们在您自己的应用程序中执行身份验证,请浏览到 JSON Web 令牌登录页面 ,网址为 Auth0。

验证和验证 JWT 之间的区别

JSON Web 令牌 (JWT) 验证和验证对于安全性至关重要,但它们解决的 JWT 安全性方面略有不同:验证可确保令牌格式正确并包含可执行的声明;验证可确保令牌是真实且未修改的。

让我们更详细地探讨验证和验证的不同之处:

JWT 验证通常是指检查 JWT 的结构、格式和内容:

  • 结构 :确保令牌具有由点分隔的标准三部分(标头、有效负载、签名)。
  • 格式 :验证每个部分是否编码正确 (Base64URL) 以及有效负载是否包含预期的声明。
  • 内容 :检查有效负载中的声明是否正确,例如过期时间 (exp)、颁发时间 (iat)、不早于 (nbf) 等,以确保令牌未过期、未在其时间之前使用等。

另一方面,JWT 验证涉及确认令牌的真实性和完整性:

  • 签名验证 :这是验证的主要方面,其中 JWT 的签名部分根据标头和有效负载进行检查。这是使用标头中指定的算法(如 HMAC、RSA 或 ECDSA)和密钥或公钥完成的。如果签名与预期不匹配,则令牌可能已被篡改或不是来自受信任的来源。
  • 颁发者验证 :检查 iss 声明是否与预期的颁发者匹配。
  • 受众检查 :确保 aud 声明与预期受众匹配。

在实践中:

验证 JWT 以确保令牌有意义、符合预期标准、包含正确的数据。

验证 JWT 以确保令牌未被恶意更改并且来自受信任的来源。

在许多系统中,这些步骤通常被合并为俗称的“JWT 验证”,其中包括验证和全面安全检查的验证。尽管如此,它们的区别仍然存在。

解码和编码 JWT 之间的区别

对 JWT 进行编码涉及将标头和有效负载转换为紧凑的 URL 安全格式。声明签名算法和令牌类型的标头以及有效负载(包括主题、到期时间和颁发时间等声明)都转换为 JSON,然后进行 Base64URL 编码。然后,这些编码部分与一个点连接,然后使用标头中指定的算法和密钥或私钥生成签名。此签名也经过 Base64URL 编码,从而生成最终的 JWT 字符串,该字符串以适合传输或存储的格式表示令牌。

解码 JWT 通过将 Base64URL 编码的标头和有效负载转换回 JSON 来反转此过程,从而允许任何人在不需要密钥的情况下读取这些部分。但是,在这种情况下,“解码”通常扩展到包括验证令牌的签名。此验证步骤包括使用最初使用的相同算法和密钥对解码的标头和有效负载重新签名,然后将此新签名与 JWT 中包含的签名进行比较。如果它们匹配,则确认令牌的完整性和真实性,确保自发行以来未被篡改。

评论

评论加载中……