Skip to Content

JWT介绍

1. 什么是JWT? (What is JWT?)

想象一下你去一个大型游乐园。在入口处,你买了一张票,工作人员验证后给了你一个防水手环。之后,无论你想玩哪个项目(过山车、旋转木马),你只需要向工作人员展示这个手环,他们看一眼就知道你是合法游客,并且手环上可能还记录了你的“快速通行”权限,而不需要每次都跑回大门口去查验你的购票记录。

在这个比喻中:

  • :就是客户端(比如浏览器、手机App)。
  • 游乐园入口:就是服务器的认证端点(比如登录接口)。
  • 你的身份证明(身份证/支付记录):就是你的用户名和密码。
  • 防水手环:就是 JWT

JWT (JSON Web Token),根据其官网(jwt.io)的定义,是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息(以JSON对象的形式)。

这里的关键词是:

  • 紧凑 (Compact):由于其体积小,可以通过 URL、POST 参数或者在 HTTP header 中传输,传输速度快。
  • 自包含 (Self-contained):Token 本身包含了所有需要验证用户身份的信息(例如用户ID、角色、过期时间等),服务器端无需再去查询数据库。
  • 安全 (Secure):信息是经过数字签名的。这意味着你可以验证信息的发送者,并确保信息在传输过程中没有被篡改。

2. 为什么需要JWT? (Why JWT?)

在 JWT 出现之前,最常见的 Web 认证方式是基于 Session-Cookie 的机制。

传统 Session-Cookie 认证流程:

  1. 客户端用用户名密码登录。
  2. 服务器验证通过后,创建一个 Session,并将 Session ID 存放在服务器内存或数据库(如 Redis)中。
  3. 服务器通过 Set-Cookie 响应头将这个 Session ID 发送给客户端。
  4. 客户端浏览器自动将这个 Cookie 存储起来。
  5. 之后每次请求,浏览器都会自动带上这个 Cookie。
  6. 服务器收到请求后,从 Cookie 中拿到 Session ID,再去自己的存储中查找对应的 Session 信息,从而判断用户身份。

这种方式的弊端:

  • 状态依赖 (Stateful):服务器需要存储 Session 信息,这增加了服务器的存储开销。
  • 扩展性差 (Poor Scalability):如果有多台服务器做负载均衡,需要解决 Session 共享问题。常见的方案是使用“粘性会话”(把用户请求固定到同一台服务器)或引入一个集中的 Session 存储(如 Redis),这都增加了架构的复杂性。
  • 跨域认证不便 (CORS Issues):Cookie 存在跨域限制,在微服务架构或前后端分离的应用中,处理跨域认证会很麻烦。

JWT 的出现正是为了解决这些问题: JWT 是无状态的 (Stateless)。服务器端不存储任何 Session 信息。所有信息都编码在 Token 自身,服务器只需要验证 Token 的合法性即可。这使得它在分布式系统、微服务架构中表现得非常出色。


3. JWT 的结构 (The Structure of a JWT)

一个 JWT 实际上是一个由三部分组成的字符串,这三部分之间用点 . 分隔,形式如下: xxxxx.yyyyy.zzzzz

这三部分分别是:

  1. Header (头部)
  2. Payload (载荷)
  3. Signature (签名)

第一部分:Header (头部)

Header 通常由两部分组成:

  • typ (Type): 令牌的类型,固定为 “JWT”。
  • alg (Algorithm): 使用的签名算法,例如 HS256 (HMAC SHA256) 或 RS256 (RSA SHA256)。

一个典型的 Header (JSON 格式):

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

这部分 JSON 会被进行 Base64Url 编码,形成 JWT 的第一部分 xxxxx

第二部分:Payload (载荷)

Payload 包含了要传递的数据,这些数据被称为 声明 (Claims)。声明是关于实体(通常是用户)和其他数据的陈述。声明有三种类型:

  • 注册声明 (Registered Claims):这些是预定义的一组声明,非强制性,但建议使用,以提供一组有用的、可互操作的声明。

    • iss (Issuer): 签发人
    • sub (Subject): 主题(通常是用户的ID)
    • aud (Audience): 接收方
    • exp (Expiration Time): 过期时间(极其重要!
    • nbf (Not Before): 生效时间
    • iat (Issued At): 签发时间
    • jti (JWT ID): 唯一身份标识,可用于防止重放攻击
  • 公共声明 (Public Claims):可以随意定义,但为了避免冲突,应该在 IANA JSON Web Token Registry 中定义,或定义为包含命名空间的 URI。

  • 私有声明 (Private Claims):这是在签发方和接收方之间共享使用的声明,既不属于注册声明,也不属于公共声明。这是我们最常用来存放自定义信息的地方。

    • 例如:userId, username, role

一个典型的 Payload (JSON 格式):

{ "sub": "1234567890", "name": "John Doe", "role": "admin", "iat": 1516239022, "exp": 1516242622 }

注意: Payload 部分也是进行 Base64Url 编码,形成 JWT 的第二部分 yyyyy。这意味着 Payload 中的信息是公开可读的,任何人都可以解码!因此,绝对不要在 Payload 中存放敏感信息,如密码!

第三部分:Signature (签名)

签名部分是 JWT 安全性的核心。它的生成规则如下:

  1. 将编码后的 Header 和编码后的 Payload 用点 . 连接起来。 encodedHeader + "." + encodedPayload
  2. 使用 Header 中指定的签名算法 (alg) 和一个密钥 (Secret) 对这个连接后的字符串进行加密/签名。

例如,如果使用 HS256 算法,签名的伪代码是: Signature = HMACSHA256(encodedHeader + "." + encodedPayload, secret)

这个 secret 是一个保存在服务器端的密钥,绝对不能泄露给客户端

签名的作用:

  • 防篡改:如果有人修改了 Header 或 Payload,由于他们没有 secret,无法生成正确的签名。服务器在验证时,会用同样的算法和 secret 重新计算签名,如果与收到的签名不匹配,就说明 Token 被篡改了,请求无效。
  • 验证来源:确保了 Token 是由你的服务器签发的,而不是伪造的。

4. JWT 的工作流程 (How JWT Works)

  1. 用户登录:用户使用用户名和密码发起 POST 请求。
  2. 服务器验证:服务器验证凭据是否正确。
  3. 生成并签发 Token:如果验证通过,服务器会创建一个包含用户标识(如用户ID、角色等)的 Payload,选择一个签名算法,然后用密钥生成签名,最终组装成一个完整的 JWT。
  4. 发送 Token 给客户端:服务器将生成的 JWT 返回给客户端。
  5. 客户端存储 Token:客户端(如浏览器)收到 JWT后,通常会将其存储在 localStoragesessionStorageHttpOnly 类型的 Cookie 中。
  6. 在后续请求中携带 Token:客户端在之后向服务器发送的每一个需要认证的请求时,都会在 HTTP Header 的 Authorization 字段中携带这个 JWT,格式通常是 Bearer <token>
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd...
  7. 服务器验证 Token:服务器收到请求后,会从 Authorization 头中取出 Token。
    • 首先,检查签名。用相同的算法和密钥重新计算签名,并与 Token 中的签名进行比对。如果一致,说明 Token 未被篡改且来源可信。
    • 然后,检查 Payload 中的声明,如 exp 是否已过期,issaud 是否正确等。
  8. 处理请求:如果所有验证都通过,服务器就认为该用户是合法的,然后从 Payload 中获取用户信息(如用户ID),执行相应的操作,并返回结果。

5. JWT 的优缺点 (Pros and Cons)

优点 (Pros)

  • 无状态与可扩展性:这是最大的优点。服务器不需要保存会话状态,使其易于水平扩展。
  • 自包含性:Token 包含了所有必要的用户信息,减少了数据库的查询次数。
  • 解耦与跨服务:非常适用于微服务架构,一个认证服务签发 Token,其他业务服务只需验证 Token 即可,服务之间高度解耦。
  • 多平台适用:天然支持跨域,对 Web、移动端(iOS/Android)等多种客户端都非常友好。

缺点 (Cons)

  • 无法主动失效:一旦一个 JWT 被签发,在它的过期时间(exp)到达之前,它就一直是有效的。你无法像 Session 一样在服务器端直接销毁它。如果一个 Token 泄露了,在它过期前,攻击者都可以用它来访问系统。
    • 解决方案
      1. 设置较短的过期时间:比如 15 分钟,并配合“刷新令牌 (Refresh Token)”机制。
      2. 建立黑名单 (Blocklist):在服务器端建立一个存储已失效 Token 的列表(比如存在 Redis 中)。每次验证 Token 时,先查一下它是否在黑名单里。但这又回到了“状态化”的老路,违背了 JWT 的初衷。
  • Token 体积:如果 Payload 中存放了太多信息,会导致 Token 变得很大,增加每次请求的流量开销。
  • 安全性:签名用的 secret 密钥一旦泄露,整个系统的安全就荡然无存。另外,Payload 是明文的,不应存放敏感数据。

6. 安全注意事项与最佳实践

  1. 密钥安全secret 必须保密,且足够复杂。绝不能硬编码在代码中,应使用环境变量或配置文件管理。
  2. 使用 HTTPS:始终使用 HTTPS 协议传输 JWT,防止中间人攻击窃取 Token。
  3. 设置过期时间 (exp):必须为 Token 设置一个合理的过期时间,且不宜过长。
  4. 使用强签名算法:避免使用 none 算法(这是一个历史漏洞,需要服务器端强制校验 alg 字段)。推荐使用 RS256(非对称加密)而非 HS256(对称加密),因为 RS256 只需要将公钥分发给各个服务进行验证,私钥可以安全地保留在认证服务中,更加安全。
  5. Payload 不放敏感数据:重申,密码、银行卡号等绝对不能放入 Payload。
  6. 客户端存储安全
    • localStorage/sessionStorage:易于使用,但容易受到 XSS (跨站脚本) 攻击。如果网站有 XSS 漏洞,攻击者可以轻易窃取 Token。
    • HttpOnly Cookie:可以有效防止 XSS 攻击,因为 JS 无法读取 HttpOnly 的 Cookie。但需要处理 CSRF (跨站请求伪造) 攻击(例如使用 SameSite 属性或 CSRF Token)。
  7. 处理 Token 失效问题:采用“短生命周期的 Access Token + 长生命周期的 Refresh Token”是目前业界公认的最佳实践,可以在安全性和用户体验之间取得很好的平衡。

总结 (Conclusion)

JWT 是一种强大而灵活的认证和授权机制,特别适合现代的分布式、微服务和无状态应用架构。它通过自包含的、经过签名的 JSON 对象,实现了无状态的身份验证。

然而,它的无状态特性也是一把双刃剑,带来了 Token 无法主动失效的问题。因此,在使用 JWT 时,必须充分理解其工作原理、优缺点,并遵循安全最佳实践,特别是密钥管理、过期时间设置和应对 Token 泄露的策略,才能构建出既高效又安全的系统。

JWT集成

环境准备

  • Spring Boot 3.x+
  • Java 17+
  • Maven 或 Gradle

第1步:添加依赖 (Dependencies)

在你的 pom.xml 文件中,确保有以下依赖:

<dependencies> <!-- Spring Boot Web Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Security Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JJWT (Java JWT Library) --> <!-- 我们需要三个 jjwt 的依赖,分别用于 API、实现和 JSON 解析 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if you prefer --> <version>0.12.5</version> <scope>runtime</scope> </dependency> <!-- for testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>

第2步:创建 JWT 工具类 (JwtUtils)

这个类是 JWT 功能的核心,负责所有与 Token 相关的操作。

package com.example.security.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; // 从 Token 中提取用户名 public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } // 从 Token 中提取过期时间 public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } // 通用的从 Token 中提取声明的方法 public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } // 解析 Token 获取所有声明 private Claims extractAllClaims(String token) { return Jwts.parser() .verifyWith(getSigningKey()) // 使用密钥验证 .build() .parseSignedClaims(token) .getPayload(); } // 检查 Token 是否过期 private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } // 为指定用户生成 Token public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); // 你可以在这里添加额外的 claims,例如角色 // claims.put("role", userDetails.getAuthorities()); return createToken(claims, userDetails.getUsername()); } // 创建 Token 的核心逻辑 private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .claims(claims) // 设置自定义声明 .subject(subject) // 设置主题(通常是用户名) .issuedAt(new Date(System.currentTimeMillis())) // 设置签发时间 .expiration(new Date(System.currentTimeMillis() + expiration)) // 设置过期时间 .signWith(getSigningKey()) // 使用密钥和算法签名 .compact(); } // 验证 Token 是否有效 public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); // 检查用户名是否匹配且 Token 未过期 return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } // 获取签名密钥 private SecretKey getSigningKey() { // 使用你的 secret 字符串生成一个安全的密钥 byte[] keyBytes = secret.getBytes(); return Keys.hmacShaKeyFor(keyBytes); } }

在你的 application.properties (或 .yml) 文件中配置密钥和过期时间:

# 使用一个足够长的、随机的字符串作为密钥! # 你可以通过在线工具生成一个安全的密钥。 jwt.secret=aVeryLongAndSecureSecretKeyForMySpringBootApplicationThatNobodyShouldKnow # 过期时间(毫秒),例如:1小时 = 3600000 jwt.expiration=3600000

第3步:创建 JWT 认证过滤器 (JwtAuthFilter)

这个过滤器是 Spring Security 与 JWT 集成的桥梁。它会在每个请求到达受保护资源之前执行,检查是否存在有效的 JWT。

package com.example.security.jwt; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class JwtAuthFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); final String jwt; final String username; // 1. 检查 Header 是否存在或格式是否正确 if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); // 如果不合法,直接跳过此过滤器,交给后续过滤器处理 return; } // 2. 提取 JWT jwt = authHeader.substring(7); // 3. 从 JWT 中提取用户名 try { username = jwtUtils.extractUsername(jwt); } catch (Exception e) { // Token 解析失败(例如过期、签名错误) // 可以在这里处理异常,例如返回 401 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } // 4. 验证 Token // 如果用户名存在,并且当前 SecurityContext 中没有认证信息 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 从数据库加载用户信息 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // 验证 Token 是否有效 if (jwtUtils.validateToken(jwt, userDetails)) { // 如果有效,创建一个认证对象 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, // 密码我们不需要,因为是 Token 认证 userDetails.getAuthorities() ); // 设置认证细节 authToken.setDetails( new WebAuthenticationDetailsSource().buildDetails(request) ); // 将认证信息放入 SecurityContext,表示该用户已认证 SecurityContextHolder.getContext().setAuthentication(authToken); } } // 5. 继续过滤器链 filterChain.doFilter(request, response); } }

第4步:配置 Spring Security (SecurityConfig)

这是最关键的一步,我们需要配置 Spring Security 来:

  • 禁用默认的 session 管理,采用无状态(Stateless)策略。
  • 配置哪些 URL 是公开的,哪些需要认证。
  • 将我们自定义的 JwtAuthFilter 添加到过滤器链中。
package com.example.security.config; import com.example.security.jwt.JwtAuthFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private JwtAuthFilter jwtAuthFilter; @Autowired private UserDetailsService userDetailsService; // 配置安全过滤器链 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 禁用 CSRF,因为我们使用 JWT,是无状态的 .csrf(AbstractHttpConfigurer::disable) // 配置 URL 的授权规则 .authorizeHttpRequests(auth -> auth // 允许对 /api/auth/** 的所有请求,无需认证 .requestMatchers("/api/auth/**").permitAll() // 其他所有请求都需要认证 .anyRequest().authenticated() ) // 配置 Session 管理策略为无状态 (STATELESS) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 添加我们自定义的 JWT 过滤器 // 它会在 UsernamePasswordAuthenticationFilter 之前执行 .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } // 密码编码器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 认证提供者,用于从数据库获取用户信息并进行密码比对 @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } // 认证管理器,用于处理登录请求 @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }

注意: 你需要提供一个 UserDetailsService 的实现,它负责从你的数据库中根据用户名加载用户信息。Spring Security 会用它来验证登录凭据和在 JwtAuthFilter 中加载用户信息。

例如,一个简单的内存 UserDetailsService 实现:

@Bean public UserDetailsService userDetailsService() { UserDetails user = User.builder() .username("user") .password(passwordEncoder().encode("password")) .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password(passwordEncoder().encode("admin")) .roles("ADMIN", "USER") .build(); return new InMemoryUserDetailsManager(user, admin); }

在实际项目中,你应该从数据库中查询用户。


第5步:创建认证端点 (AuthController)

最后,我们需要一个 API 接口,让用户可以通过用户名和密码登录,并获取一个 JWT。

package com.example.security.controller; import com.example.security.dto.AuthRequest; import com.example.security.dto.AuthResponse; import com.example.security.jwt.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUtils jwtUtils; @PostMapping("/login") public AuthResponse createAuthenticationToken(@RequestBody AuthRequest authRequest) throws Exception { // 1. 使用 AuthenticationManager 进行用户认证 Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); // 2. 如果认证通过,SecurityContextHolder 会保存认证信息 // 我们可以从中获取 UserDetails final UserDetails userDetails = (UserDetails) authentication.getPrincipal(); // 3. 使用 JwtUtils 生成 JWT final String jwt = jwtUtils.generateToken(userDetails); // 4. 返回 JWT return new AuthResponse(jwt); } // 创建 DTOs (Data Transfer Objects) // AuthRequest.java // record AuthRequest(String username, String password) {} // AuthResponse.java // record AuthResponse(String jwt) {} } // DTOs package com.example.security.dto; public record AuthRequest(String username, String password) {} package com.example.security.dto; public record AuthResponse(String jwt) {}

整体流程回顾

  1. 用户登录

    • 客户端向 /api/auth/login 发送包含用户名和密码的 POST 请求。
    • AuthController 调用 AuthenticationManager 来验证凭据。
    • AuthenticationManager 使用 DaoAuthenticationProvider,它会调用你的 UserDetailsService 获取用户信息,并用 PasswordEncoder 比较密码。
    • 如果认证成功,AuthController 调用 JwtUtils 生成一个 JWT。
    • 服务器将 JWT 返回给客户端。
  2. 访问受保护资源

    • 客户端在后续请求的 Authorization Header 中携带 JWT,格式为 Bearer <token>
    • JwtAuthFilter 拦截到请求,从 Header 中提取出 Token。
    • JwtUtils 验证 Token 的签名和有效期。
    • 如果 Token 有效,过滤器从 Token 中解析出用户名,并使用 UserDetailsService 加载用户完整信息。
    • 过滤器创建一个 UsernamePasswordAuthenticationToken 对象,并将其设置到 SecurityContextHolder 中。
    • 由于 SecurityContext 中有了合法的 Authentication 对象,Spring Security 认为该请求已经过认证,允许其访问受保护的 Controller 方法。
Last updated on