首页
Preview

使用Spring Boot和Spring Security实现JWT

本文作者是Mauricio Urraco,全栈开发者 @ XOOR

你可以在这里找到本教程的代码。

让我们先来了解一下这个项目的文件结构:

文件结构

spring-boot-jwt/
 │
 ├─ src/main/java/
 │  └─ murraco
 │     ├─ configuration
 │     │  └─ SwaggerConfig.java
 │     │
 │     ├─ controller
 │     │  └── UserController.java
 │     │
 │     ├─ dto
 │     │  ├── UserDataDTO.java
 │     │  └── UserResponseDTO.java
 │     │
 │     ├─ exception
 │     │  ├── CustomException.java
 │     │  └── GlobalExceptionController.java
 │     │
 │     ├─ model
 │     │  ├── Role.java
 │     │  └── User.java
 │     │
 │     ├─ repository
 │     │  └─ UserRepository.java
 │     │
 │     ├─ security
 │     │  ├── JwtTokenFilter.java
 │     │  ├── JwtTokenFilterConfigurer.java
 │     │  ├── JwtTokenProvider.java
 │     │  ├── MyUserDetails.java
 │     │  └── WebSecurityConfig.java
 │     │
 │     ├─ service
 │     │  └── UserService.java
 │     │
 │     └─ JwtAuthServiceApp.java
 │
 ├── src/main/resources/
 │   └── application.yml
 │
 ├── LICENSE
 ├── mvnw/mvnw.cmd
 ├── README.md
 └── pom.xml

简介 (https://jwt.io)

首先,我们先来看一下来自jwt.io的简介:

什么是 JSON Web Token?

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

接下来我们来详细解释一下这个定义中的一些概念。

紧凑 (Compact):由于它们更小的大小,JWT 可以通过 URL、POST 参数或 HTTP 头部内发送。此外,由于其更小的大小,传输速度也更快。

自包含 (Self-contained):有效载荷包含有关用户的所有所需信息,避免了多次查询数据库的需求。

什么时候应该使用 JSON Web Token?

下面是一些使用 JSON Web Token 有用的场景:

身份验证 (Authentication):这是使用 JWT 最常见的场景。一旦用户登录成功,每个后续请求都将包含 JWT,允许用户访问允许使用该令牌的路由、服务和资源。现在,单点登录是一个广泛使用 JWT 的功能,因为它的开销小且易于在不同域之间使用。

信息交换 (Information Exchange):JSON Web Token 是在各方之间安全传输信息的好方法。由于 JWT 可以被签名 - 例如,使用公钥/私钥对 - 你可以确定发送方是其所说的人。此外,由于签名是使用头部和有效载荷计算的,因此你还可以验证内容未被篡改。

JSON Web Token 的结构是什么?

JSON Web Token 由三部分组成,由点 (.) 分隔,它们是:

  • 头部 (Header)
  • 有效载荷 (Payload)
  • 签名 (Signature)

因此,一个 JWT 通常如下所示。

xxxxx.yyyyy.zzzzz

接下来,我们将分解不同的部分。

头部 (Header)

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

例如:

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

然后,将此 JSON 进行 Base64Url 编码,以形成 JWT 的第一部分。

有效载荷 (Payload)

令牌的第二部分是有效载荷,其中包含声明 (Claims)。声明是关于实体 (通常是用户) 和其他元数据的陈述。声明有三种类型:保留 (Reserved)、公共 (Public) 和私有 (Private) 声明。

  • 保留声明 (Reserved Claims):这些是一组预定义的声明,它们不是强制性的,但建议提供一组有用的、可互操作的声明。其中一些是:iss (发行者),exp (过期时间),sub (主题),aud (受众) 等。

请注意,声明名称仅为三个字符长,因为 JWT 应该是紧凑的。

  • 公共声明 (Public Claims):这些可以由使用 JWT 的人自行定义。但为了避免冲突,它们应在 IANA JSON Web Token 注册表中定义,或者定义为包含冲突防止命名空间的 URI。
  • 私有声明 (Private Claims):这些是为共享信息而创建的自定义声明,由同意使用它们的各方创建。

有效载荷的一个示例可能是:

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

然后,将有效载荷进行 Base64Url 编码,以形成 JSON Web Token 的第二部分。

签名 (Signature)

要创建签名部分,必须使用编码的头部、编码的有效载荷、一个秘密、头部中指定的算法进行签名。

例如,如果要使用 HMAC SHA256 算法,则将创建如下签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证 JWT 的发送方是其所说的人,并确保消息在传输过程中没有被更改。将所有内容放在一起

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

以下显示了一个已编码头部和有效载荷,并使用秘密签名的 JWT。已编码 JWT:

JSON Web Token 是如何工作的?

在身份验证中,当用户成功使用其凭据登录时,将返回 JSON Web Token 并必须在本地保存(通常在本地存储中,但也可以使用 cookie),而不是创建服务器上的会话并返回 cookie 的传统方法。

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

Authorization: Bearer <token>

这是一种无状态的身份验证机制,因为用户状态从未保存在服务器内存中。服务器的受保护路由将检查 Authorization 头部中是否存在有效的 JWT,如果存在,则允许用户访问受保护的资源。由于 JWT 是自包含的,所有必要的信息都在那里,因此减少了多次查询数据库的需要。``` 这使你能够完全依赖无状态的数据API,甚至向下游服务发出请求。无论哪个域正在提供你的API,跨域资源共享(CORS)都不是问题,因为它不使用cookie。

下图展示了这个过程:

JWT身份验证摘要

基于令牌的身份验证模式近来变得非常流行,因为与会话/cookie相比,它们提供了重要的好处:

  • CORS
  • 无需CSRF保护
  • 更好的移动端集成
  • 减少授权服务器负载
  • 无需分布式会话存储

这种方法需要做出一些权衡:

  • 更容易受到XSS攻击
  • 访问令牌可能包含过时的授权声明(例如,当撤销某些用户权限时)
  • 访问令牌在声明数量增加时可能会增大
  • 文件下载API可能难以实现
  • 真正的无状态和吊销是相互排斥的

JWT身份验证流程非常简单

  • 用户通过向授权服务器提供凭据来获取刷新和访问令牌
  • 用户在每个请求中发送访问令牌以访问受保护的API资源
  • 访问令牌被签名并包含用户身份(例如,用户ID)和授权声明。 需要注意的是,授权声明将与访问令牌一起包含。为什么这很重要?假设授权声明(例如,数据库中的用户特权)在访问令牌的生命周期内发生更改。这些更改在新的访问令牌发行之前不会生效。在大多数情况下,这不是大问题,因为访问令牌的寿命很短。否则,请使用不透明令牌模式。

实现细节

让我们看看如何使用Java和Spring实现基于JWT令牌的身份验证,并尝试重用Spring安全默认行为。Spring Security框架提供了插件类,可以处理授权机制,如:会话cookie,HTTP Basic和HTTP Digest。尽管如此,它缺乏原生支持JWT,我们需要自己动手让它工作。

MySQL数据库

此演示目前使用由Spring Boot自动配置的名为user_db的MySQL数据库。如果要连接到另一个数据库,你必须在资源目录中的application.yml文件中指定连接。请注意,hibernate.hbm2ddl.auto=create-drop将在每次部署时删除并创建一个干净的数据库(如果你在实际项目中使用此功能,则可能需要更改它)。以下是项目中的示例:

代码

  • JwtTokenFilter
  • JwtTokenFilterConfigurer
  • JwtTokenProvider
  • MyUserDetails
  • WebSecurityConfig JwtTokenFilter

JwtTokenFilter过滤器应用于每个API(/**),但不包括signin token端点(/users/signin)和singup端点(/users/signup)。

此过滤器具有以下职责:

  • 检查Authorization头中的访问令牌。如果在头中找到访问令牌,则委托身份验证给JwtTokenProvider,否则抛出身份验证异常。
  • 根据JwtTokenProvider执行的身份验证过程的结果调用成功或失败策略 请确保在成功身份验证时调用chain.doFilter(request, response)。你希望请求的处理进入下一个过滤器,因为最后一个过滤器_FilterSecurityInterceptor#doFilter_实际上负责调用处理所请求的API资源的控制器中的方法。

JwtTokenFilterConfigurer

JwtTokenFilter添加到spring boot security的DefaultSecurityFilterChain中。

JwtTokenProvider

JwtTokenProvider具有以下职责:

  • 验证访问令牌的签名
  • 从访问令牌中提取身份和授权声明,并使用它们创建UserContext
  • 如果访问令牌格式不正确,过期或仅仅是未使用适当的签名密钥签名,则会抛出身份验证异常。 MyUserDetails

实现UserDetailsService以定义我们自己的自定义loadUserbyUsername函数。UserDetailsService接口用于检索与用户相关的数据。它有一个名为loadUserByUsername()的方法,该方法根据用户名查找用户实体,并可以重写以自定义查找用户的过程。

在身份验证期间,DaoAuthenticationProvider使用它来加载有关用户的详细信息。

WebSecurityConfig

WebSecurityConfig类扩展WebSecurityConfigurerAdapter以提供自定义安全配置。

在此类中配置和实例化以下bean:

  • JwtTokenFilter
  • PasswordEncoder 此外,在WebSecurityConfig#configure(HttpSecurity http)方法中,我们将配置模式以定义受保护/未受保护的API端点。请注意,我们已禁用CSRF保护,因为我们不使用Cookies。

结论

正如本文所示,借助Spring Boot和Spring Security,我们可以在短时间内拥有一个JWT身份验证服务。如果利用Spring Cloud和Netflix Stack:Eureka,Zuul,Ribbon和Hystrix,这也非常容易集成到微服务生态系统中(我们将在未来的文章中看到更多信息)。同时,请记得检查GitHub上的代码以尝试它,或将JWT身份验证添加到你自己的项目中!

译自:https://medium.com/@xoor/jwt-authentication-service-44658409e12c

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
阿波
The minute I see you, I want your clothes gone!

评论(0)

添加评论