本文作者为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
评论(0)