首页
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使用公钥/私钥对进行签名。

让我们进一步解释一些这个定义中的概念。

紧凑: 由于它们体积小,因此可以通过URL、POST参数或HTTP头内部发送JWT。此外,体积小意味着传输速度快。

自包含: 负载包含有关用户的所有必需信息,避免了需要多次查询数据库的情况。

何时使用 JSON Web Token?

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

认证: 这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是现在广泛使用JWT的功能,因为它的开销小,可以轻松地在不同的域之间使用。

信息交换: JSON Web Token是在各方之间安全传输信息的好方法。由于JWT可以签名,例如使用公钥/私钥对,因此你可以确定发送方是谁。此外,由于签名是使用头部和负载计算的,因此你还可以验证内容是否被篡改。

JSON Web Token 的结构是什么?

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

  • 头部
  • 负载
  • 签名

因此,JWT通常看起来像下面这样。

xxxxx.yyyyy.zzzzz

让我们分解不同的部分。

头部

头部通常由两部分组成:令牌类型,即JWT,以及正在使用的哈希算法,例如HMAC SHA256或RSA。

例如:

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

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

负载

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

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

请注意,声明名称只有三个字符长,因为JWT旨在紧凑。

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

负载的示例可能是:

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

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

签名

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

例如,如果要使用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,通常在使用Bearer模式的Authorization头中。标头的内容应如下所示:

Authorization: Bearer <token>

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

下图显示了这个过程:

JWT认证摘要

基于令牌的认证架构最近变得非常流行,因为与会话/cookies相比,它们提供了重要的优点:

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

这种方法必须做出一些权衡:

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

JWT认证流程非常简单

  • 用户通过向授权服务器提供凭据来获取刷新和访问令牌
  • 用户发送访问令牌以访问受保护的API资源
  • 访问令牌已签名,并包含用户标识(例如用户ID)和授权声明。

重要的是要注意,授权声明将包含在访问令牌中。为什么这很重要?好吧,假设授权声明(例如数据库中的用户权限)在访问令牌的生命周期内发生了更改。这些更改在发行新的访问令牌之前不会生效。在大多数情况下,这不是什么大问题,因为访问令牌的寿命很短。否则,请使用不透明令牌模式。

实现细节

让我们看看如何使用Java和Spring实现基于JWT令牌的身份验证,同时尝试重用Spring安全性的默认行为。Spring Security框架带有插件类,已经处理授权机制,如:会话cookie,HTTP基本和HTTP摘要。尽管如此,它缺少对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安全性的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)

添加评论