应用程序的安全性是需要考虑的最重要问题之一。
每天有数百万用户在互联网上发布敏感信息,保护这些信息安全是开发人员面临的最大挑战之一。幸运的是,这不是一个新问题,我们有几种工具可以用来保护我们应用程序用户的数据安全。其中一种特别实用且易于实现的工具是 JWT(JSON Web Tokens)。
描述
在本文中,我们将学习如何使用 JWT 和 Spring Boot 实现基本的用户身份验证,但首先我们需要知道 JWT 是什么。如果我们快速搜索一下谷歌,我们会发现 JWT 是一个 RFC 7519 的开放标准。如果你打开那个链接,你会看到这个定义:
JSON Web Token (JWT) 是一种紧凑且 URL 安全的方式,用于表示要在两个方之间传输的声明。JWT 中的声明被编码为 JSON 对象,该对象用作 JSON Web Signature (JWS) 结构的有效负载或 JSON Web Encryption (JWE) 结构的明文,使声明能够被数字签名或使用消息认证码 (MAC) 进行完整性保护和/或加密。
这种定义有时可能比它们实际上的复杂程度更复杂,因此我们将尝试以更简单的方式解释它。在任何具有私人信息或受限制访问的应用程序中,我们需要一种验证请求的方法,而无需为每个请求处理客户端的登录凭据。为此,我们有 JWT,它可以在没有 cookie 或登录信息的情况下验证用户。
解释
当用户登录时,后端生成一个 JWT,组成如下:
header.payload.signature
头
我们可以看到 JWT 以头部开始,其中包含有关如何解释令牌的所有相关信息。
负载
有效载荷包含声明。简而言之,声明是用户数据(或任何实体)以及添加到令牌中的附加重要信息(非强制性),这些信息增加了令牌的功能。
我们可以找到三种类型的声明:注册声明、公共声明、私有声明。
注册声明 用于提供有关令牌的附加信息,例如创建时间或令牌过期时间。这些声明不是强制性的。
公共声明 是由使用 JWT 的人定义的。使用者应该小心使用名称,因为它们可能会导致冲突。
私有声明 可以用于存储信息,而不使用 注册声明 或 公共声明。请记住,它们容易发生冲突。
签名
签名由编码后的头部、编码后的负载、一个密钥和编码算法(也在头部中)组成。而且所有这些都是签名的。
这是 JWT 构成的大致概况。欲了解更多信息,请访问此链接以查找 JWT 及其各个部分的示例,并在你想要更详细地分析它时查看此链接。
实现
对于实现,我们将使用 Java Spring Boot 创建一个带有登录的小应用程序,我们将使用 Postman 消费该应用程序。关于持久性,我们将使用 PostgreSQL,但请记住,这仅是为了持久化用户而没有任何关系(由于目标是教授基本的会话登录,使用其他实体如角色会偏离主要概念)。
让我们将用例分为两个部分:用户登录和用户尝试消费受保护的端点。
在这个图表中,我们可以看到用户尝试使用其用户名和密码登录,请求成功并返回一个 JWT,该 JWT 将用于消费受保护的端点。
在这个图表中,我们可以看到在登录并获取 JWT 令牌后,用户会向受保护的端点发出请求,服务器会验证该 JWT,处理请求并将响应发送给客户端。
现在我们已经了解了客户端-服务器交互的工作原理,我们可以开始查看我们项目的结构:
然后让我们从 User 类开始。
在这里,我们有一个来自我们应用程序的典型 User 类。如前所述,我们省略了角色的实现,以避免偏离主要概念。
在这种情况下,我们有一个表示数据库中用户的 ID,我们有一个用户名和一个密码,这将允许我们持久化用户的凭据并在登录过程中使用它们。
为了持久化用户,我们将使用一个存储库。在这个存储库中,我们将有一个允许我们根据用户名获取用户的方法,这对身份验证过程将非常有用。
这个控制器将负责使用用户名和密码在数据库中注册用户。重要的是要注意,我们正在加密密码,以便我们的数据库不包含实际的密码信息。
这将是我们的安全控制器,用于测试身份验证是否成功以及我们是否获得了正常情况下没有访问权限的响应。
Spring Boot 安全配置和过滤器
首先,我们将开始定义一些我们将在实现中使用的常量。
在这里,我们可以看到一系列重要的数据:
- SIGN_UP_URL:确定公共端点以注册用户。
- KEY:包含用于签署令牌的密钥,并且它的长度为 512 字节,因为它将被用于需要至少该长度的字符串的算法。(通常密钥将从秘密中获得,永远不会硬编码)。
- HEADER_NAME:包含你在请求时要将 JWT 添加到的标头的名称。
- EXPIRATION_DATE:包含令牌在过期之前有效的时间(以毫秒为单位)。这个类包含了Spring Boot安全性的配置。通过这个配置,我们可以指定很多内容,例如可以在哪个url注册用户、哪些url是受保护的以及我们想要使用什么类型的会话进行认证等等。
接下来我们将看到这个类中最重要的配置。
首先我们可以看到的是注解 @EnableWebSecurity。这个注解激活了Spring Boot中集成的Web安全性,我们将会修改这个配置。
你还记得我们在 SecurityConstants 类中定义的常量 SIGN_UP_URL 吗?这个常量在这个类中的 configure(HttpSecurity http) 方法中被用来定义我们可以在哪个端点中注册用户。此外,这个方法还负责非常重要的配置,例如:CORS 的配置、身份验证 和 授权 过滤器的定义(我们稍后会看到它们的实现),以及将会话设为无状态(这避免了在响应中发送Cookie以处理会话)。在 CorsConfigurationSource corsConfigurationSource() 方法中,我们将允许所有url支持端点CORS,这将允许我们将其限制为仅限于某些或没有。
最后,configure(AuthenticationManagerBuilder auth) 方法允许我们使用自定义的服务实现,在认证正确时获取用户数据(我们将在接下来看到此服务的实现)。
这个服务负责将用户在数据库中的数据与提交的凭证进行比较,如果匹配,则对用户进行身份验证。需要强调的是,这个方法实例化了一个 User 对象,这个对象被 Spring Boot 安全核心用于生成用户会话。关于这个,请访问这个链接获取更多信息。
现在我们已经拥有了从数据库获取用户数据所需的服务以及 Spring Boot 生成JWT令牌所需的必要配置,接下来我们将看到两个非常重要的类:第一个负责认证用户(在登录正确时生成一个在标头中发送的令牌),第二个则验证客户端发送的令牌以允许其访问受保护的端点。
在这个类中,我们可以看到它继承了“UsernamePasswordAuthenticationFilter”,这是一个负责处理用户会话的类。浏览这个类,我们可以看到我们实例化了一个“AuthenticationManager”类的对象,我们将在“attemptAuthentication”方法中使用它来处理会话开始尝试。如果尝试成功,令牌将在“successfulAuthentication”方法中生成。首先,我们将定义过期时间(我们将使用之前定义的常量之一),然后我们将生成用于签名令牌的密钥。这个密钥也在我们的常量中定义,我们使用“hmacShaKeyFor”方法以安全的方式生成这个密钥(之前使用了文本字符串,但使用密钥对象更安全)。需要注意的是,密钥必须是特定大小的。作为示例,我们将使用加密算法“HS512”。对于这个算法,我们需要一个长度不小于512位的字符串,否则我们将得到一个异常,告诉我们这不够安全。
为了生成恰好为512位的字符串,我建议使用这个网站。
现在,我们已经看到了负责认证用户的类,接下来我们将看一下负责授权用户访问受保护端点的类。为此,这个类将验证客户端发送给我们的令牌,并检查它是否与我们的签名匹配。
在这个类中,我们可以看到“authentication”方法将检查令牌是否存在,如果存在,则检查它是否有效。如果是,则会从令牌中获取加密的用户,并将其合并到“SecurityContextHolder”中,然后运行一系列过滤器(其中一些我们在“SecurityConfiguration”类中定义),如果这些过滤器通过,则该方法将允许用户访问端点。
请注意,到目前为止,我们还没有特别定义用户登录的端点。这就是为什么当我们扩展我们的“UsernamePasswordAuthenticationFilter”授权过滤器时,Spring Boot已经预定义了它。
到这一点,我们已经实现了登录用户并授权使用受保护端点所需的所有逻辑。
测试实现
为了测试实现,我们将使用Postman。首先,我们必须注册一个用户。为此,我们将在POST中发送用户的凭证,并将它们持久化到数据库中。
如果一切顺利,我们将收到一个HTTP状态码200,确认凭证已被持久化。现在我们可以尝试登录。
我们将向“http://localhost:8089/login”发送请求,如果凭证正确,则会在标头中包含由服务器生成的令牌,并收到HTTP状态码200的响应。
现在我们已经有了证实我们身份的令牌,我们可以使用它来访问受保护的端点。让我们尝试访问之前定义的端点。这次我们将使用GET方法向“http://localhost:8089/api/secure”发送请求,但我们必须在标头中添加令牌。为此,我们将使用“Authorization”作为标识符,并在其旁边放置令牌。
如果令牌正确,则会回复一个200响应状态,以及确认我们正在阅读受保护信息的消息。
通过调用这个端点,我们可以确认我们应用中用户的认证和授权工作正常。
结论
在本文中,我们实现了一个基本的用户类,包括它的凭据,定义用户登录端点和只有授权用户才能使用的端点的控制器。为此,我们配置了Spring Boot的安全性,实现了一系列过滤器,并开发了生成我们在每个请求中发送的令牌的逻辑。
完成所有这些后,我们可以放心地使用JWT确保我们的信息的安全性。
译自:https://medium.com/wolox/securing-applications-with-jwt-spring-boot-da24d3d98f83
评论(0)