JWT
JWT(JSON Web Token)是一种基于JSON的开放标准,用于在网络上安全地传输信息。它通常用于身份验证和授权。在JWT中,可以使用对称加密和非对称加密两种方式对数据进行加密。
一、JWT概述
JWT是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。它的声明一般被用来在身份提供者和服务提供者之间传递被认证的用户身份信息,以便从资源服务器获取资源。
二、JWT组成
一个JWT实际上就是一个字符串,它由三部分组成,头部(Header)、有效载荷(Payload)和签名(Signature)。这三部分用圆点(.)分隔。
- 头部(Header)
头部承载了两部分信息:token的类型和哈希算法。
{
"alg": "HS256",
"typ": "JWT"
}
- 有效载荷(Payload)
有效载荷是存放有效信息的地方,这些信息包含了三个部分:注册声明、公共声明和私有声明。
- 注册声明:
这是在所有JWT中都存在的声明,它们不是可选的。有如下几种:
- iss: 发行人
- sub: 主题
- aud: 观众
- exp: 过期时间
- nbf: 不早于
- iat: 发行时间
- jti: JWT ID
例如:
{
"iss": "joe@example.com",
"sub": "1234567890",
"aud": "example.com",
"exp": 1356999524,
"nbf": 1456999524,
"iat": 1456999524,
"jti": "nYWyLmIQlKdBhdCgfJAhG"
}
- 公共声明:
公共声明可以由使用JWT的任何一方定义。但是,为了确保互操作性,应该限制使用专有的公共声明。以下是一些标准的公共声明:
- upn: 唯一的用户名称
- acr: 认证上下文引用
- amr: 授权域引用
- at_hash: 访问令牌的哈希值
例如:
{
"upn": "joe@example.com",
"acr": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
"amr": [
"pwd",
"cert",
"saml"
],
"at_hash": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
- 私有声明:
私有声明是自定义的声明,它可以被添加到JWT中以提供额外的信息,但并不是所有的JWT都包含私有声明。私有声明是特定应用程序或组织的需要,并且它们的用途和结构可能会根据每个特定的用例而有所不同。由于它们是私有的,因此没有预定义的标准来指定应该如何处理它们。
例如:
{
"user_id": "1234",
"username": "joe@example.com",
"email": "joe@example.com"
}
- 签名(Signature)
签名是对头部和有效载荷进行加密的过程,以防止数据被篡改。签名使用头部中指定的“alg”属性指定的算法进行计算。这个属性的值是一个字符串,指示所使用的加密算法,例如"HS256"表示HMAC SHA256。
三、JWT工作流程
JWT的工作流程可以分为以下四个步骤:
- 用户使用用户名和密码请求登录服务。
- 登录服务验证用户的身份,如果身份验证成功,则返回一个JWT。
- 用户使用JWT访问受保护的资源。
- 资源服务器验证JWT,如果验证成功,则允许用户访问资源。
四、JWT优缺点
优点:
- 简单:JWT的开销很小,易于实现。它只需要代码解析JWT,而不需要数据库查询。它使用标准JSON格式,所以可以轻松地使用各种语言进行解析。
- 基于无状态:JWT本身是无状态的,这意味着您可以随时生成新的token。这使得在多个服务器上扩展应用程序变得更加容易,因为您不需要在服务器之间同步会话信息。
- 可扩展性:由于JWT是自描述的,因此可以轻松地对其进行扩展。您只需添加新的声明即可,而无需修改现有的代码或基础架构。
缺点:
- 大小:JWT通常比简单的会话ID更大,因为它们是自描述的。这可能会导致网络延迟和带宽使用增加。
- 不可撤销:一旦签发了JWT,就无法撤销它。如果您需要禁用客户端的访问权限,您必须等待token过期。一种解决方法是在每次请求时检查token列表,但这会增加复杂性并降低性能。
- 敏感信息泄露:由于JWT是自描述的,因此它们可能会包含敏感信息。如果攻击者获得了token,他们可以访问其中的所有信息。因此,不要在JWT中包含敏感信息。
五、总结
JWT是一种用于在网络应用环境间传递声明的安全的方式。它具有简单、无状态和可扩展等优点,但也存在大小、不可撤销和敏感信息泄露等缺点。在使用JWT时,需要注意保护好私钥和敏感信息,以确保系统的安全性。
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
@EnableGlobalMethodSecurity
是一个用于开启 Spring Security 方法级别的安全性的注解。它有两个参数:securedEnabled
和 prePostEnabled
。
-
securedEnabled
:默认值为 true,表示启用基于表达式的安全性(即使用 @Secured 注解)。如果设置为 false,则禁用基于表达式的安全性。 -
prePostEnabled
:默认值为 false,表示禁用基于 PreAuthorize 和 PostAuthorize 注解的安全性。如果设置为 true,则启用基于这些注解的安全性。
举例:
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
}
在这个例子中,我们开启了基于表达式的安全性(通过设置 securedEnabled
为 true)和基于 PreAuthorize 和 PostAuthorize 注解的安全性(通过设置 prePostEnabled
为 true)。这意味着我们可以在方法上使用 @Secured
、@PreAuthorize
和 @PostAuthorize
注解来控制访问权限。
GlobalFilter
GlobalFilter
是 Spring Cloud Gateway 中的一个接口,用于全局过滤请求。当一个请求到达网关时,所有实现了 GlobalFilter
的过滤器都会被执行。GlobalFilter
提供了两个方法:filter
和 shortcut
。filter
方法用于处理请求,shortcut
方法用于决定是否跳过后续的过滤器。
举例说明:
假设我们需要实现一个简单的全局过滤器,用于记录所有请求的日志。我们可以创建一个类 LoggingGlobalFilter
并实现 GlobalFilter
接口:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class LoggingGlobalFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(LoggingGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
logger.info("请求路径: {}", request.getPath());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
在这个例子中,我们实现了 GlobalFilter
接口,并重写了 filter
方法。在 filter
方法中,我们记录了请求的路径。同时,我们还实现了 Ordered
接口,设置了过滤器的优先级。这样,当有请求到达网关时,我们的 LoggingGlobalFilter
就会被执行,记录请求的日志。