Spring Security + JWT 面试题整理

1.说说Spring Security的核心组件有哪些?

标准答案

组件 作用
SecurityContextHolder 存储当前线程的安全上下文(Authentication对象),默认使用ThreadLocal
Authentication 表示认证主体,包含principal(用户信息)、credentials(凭证)、authorities(权限)
AuthenticationManager 认证管理器接口,核心认证入口
ProviderManager AuthenticationManager的默认实现,管理多个AuthenticationProvider
AuthenticationProvider 具体认证逻辑,如DaoAuthenticationProvider查数据库验证
UserDetailsService 加载用户信息的接口,通常由开发者实现(查数据库)
UserDetails 用户信息接口,包含用户名、密码、权限等
PasswordEncoder 密码编码器,负责加密和验证密码
FilterChainProxy Spring Security的入口过滤器,管理所有SecurityFilterChain
SecurityFilterChain 一组过滤器链,决定哪些过滤器应用于当前请求
GrantedAuthority 权限标识,通常对应角色或操作权限

💡 加分回答

在Spring Boot自动配置中,SecurityAutoConfiguration会默认注入这些核心组件。我们通常只需要关注UserDetailsServicePasswordEncoder的自定义实现。


2.Spring Security的认证流程是怎样的?

标准答案(以用户名密码登录为例):

复制代码
1. 用户提交用户名密码
   ↓
2. UsernamePasswordAuthenticationFilter 拦截请求,提取用户名密码
   ↓
3. 创建 UsernamePasswordAuthenticationToken(未认证状态)
   ↓
4. 调用 AuthenticationManager.authenticate()
   ↓
5. ProviderManager 遍历 AuthenticationProvider
   ↓
6. DaoAuthenticationProvider 调用 UserDetailsService.loadUserByUsername()
   ↓
7. 获取 UserDetails(包含密码和权限)
   ↓
8. 使用 PasswordEncoder 匹配密码
   ↓
9. 匹配成功 → 创建认证后的 Authentication 对象
   ↓
10. 存入 SecurityContextHolder
   ↓
11. 后续过滤器通过 SecurityContextHolder 获取认证信息

💡 加分回答

认证失败时,会抛出AuthenticationException,由AuthenticationEntryPoint处理,返回401状态码。在JWT场景下,我们自定义过滤器替代了默认的UsernamePasswordAuthenticationFilter


3.什么是Spring Security的过滤器链?常用的过滤器有哪些?

标准答案

Spring Security基于Servlet Filter实现,通过DelegatingFilterProxy将请求委托给Spring管理的FilterChainProxy,内部维护一组有序的过滤器。

常用过滤器(按顺序)

过滤器 作用
SecurityContextPersistenceFilter 从Session中恢复/保存SecurityContext
LogoutFilter 处理登出请求
UsernamePasswordAuthenticationFilter 处理表单登录认证
BasicAuthenticationFilter 处理HTTP Basic认证
RememberMeAuthenticationFilter 处理"记住我"功能
AnonymousAuthenticationFilter 为未认证用户创建匿名Authentication
ExceptionTranslationFilter 处理认证/授权异常
FilterSecurityInterceptor 最终的授权判断(基于URL)

💡 加分回答

在JWT场景中,我们在UsernamePasswordAuthenticationFilter之前 插入自定义的JwtAuthenticationFilter,这样优先解析JWT,如果解析成功则跳过表单登录过滤器。


4.Spring Security如何实现无状态(STATELESS)?为什么需要?

标准答案

通过配置 sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 实现。

原因

  • Spring Security默认使用HttpSession存储认证信息(有状态)

  • JWT将认证信息存储在客户端,服务端无需Session

  • 设置STATELESS后,Spring Security不再创建和使用HttpSession

具体影响

  • 不会生成JSESSIONID Cookie

  • 不会从Session中读取/写入SecurityContext

  • 每次请求必须独立携带完整的认证信息

💡 加分回答

在微服务架构中,STATELESS让服务可以水平扩展,无需考虑Session共享问题。配合网关层统一认证,下游服务完全无状态。


5. JWT的结构是什么?各部分的作用?

标准答案

JWT由三部分组成,用.分隔:

复制代码
xxxxx.yyyyy.zzzzz
部分 全称 作用 编码方式
Header 头部 声明算法类型(如HS256)和Token类型 Base64Url
Payload 载荷 存储用户信息(如用户名、角色)和标准字段(exp、iat等) Base64Url
Signature 签名 对Header+Payload进行签名,防止篡改 使用指定算法+密钥

Payload中的标准字段

字段 含义
iss (issuer) 签发者
sub (subject) 主题(通常存用户名)
aud (audience) 受众
exp (expiration time) 过期时间
nbf (not before) 生效时间
iat (issued at) 签发时间
jti (JWT ID) 唯一标识

💡 加分回答

注意:Payload是Base64编码不是加密 !任何人都可以解码查看内容。所以不要存敏感信息(如密码、银行卡号)。防篡改靠的是签名。

6. JWT如何保证安全性?防止被篡改?

标准答案

防篡改机制

复制代码
        Header.Payload → 使用密钥 + 指定算法 → Signature          
  • 服务端收到JWT后,用相同密钥重新计算签名

    • 如果计算结果与Token中的Signature不一致,说明被篡改

    • 常用算法:HS256(对称加密)、RS256(非对称加密)

💡 加分回答

推荐使用RS256:私钥签名,公钥验证。微服务架构中,认证中心持有私钥,各个服务持有公钥即可验证Token,无需共享密钥,更安全。


7. Spring Security + JWT如何实现登录功能?详细流程?

标准答案(完整流程):

复制代码
【登录阶段】
1. 前端提交用户名密码到 /login
2. Spring Security的AuthenticationManager验证
3. 验证通过 → 业务代码生成JWT
4. 返回JWT给前端(前端存localStorage)

【请求阶段】
1. 前端在Authorization头携带 JWT
2. 自定义JwtAuthenticationFilter 拦截
3. 解析JWT,提取用户信息
4. 构造 Authentication 对象
5. 存入 SecurityContextHolder
6. 后续过滤器做权限判断
7. 有权限 → 执行业务逻辑;无权限 → 403

代码示例

java 复制代码
// 登录接口
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
    Authentication auth = authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
    );
    String token = jwtUtils.generateToken(auth.getName(), auth.getAuthorities());
    return ResponseEntity.ok(new JwtResponse(token));
}

// JWT过滤器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                   HttpServletResponse response, 
                                   FilterChain chain) {
        String token = extractToken(request);
        if (token != null && jwtUtils.validateToken(token)) {
            String username = jwtUtils.getUsername(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            Authentication auth = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities()
            );
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        chain.doFilter(request, response);
    }
}

💡 加分回答

关键点:

  1. JWT过滤器要放在UsernamePasswordAuthenticationFilter之前

  2. 登录接口不需要JWT,要配置permitAll()

  3. 必须设置SessionCreationPolicy.STATELESS


8. JWT如何续签?有什么方案?

标准答案

方案 实现 优缺点
方案1:双Token Access Token(短期)+ Refresh Token(长期) ✅ 安全可控,❌ 需要额外接口
方案2:滑动过期 每次请求检查剩余时间,到期前自动续签 ✅ 用户无感知,❌ 需修改Token
方案3:缩短有效期 Token设短(15分钟),过期重新登录 ❌ 用户体验差
方案4:Redis黑名单 维护黑名单,支持主动失效 ✅ 可主动控制,❌ 增加存储

方案1实现示例

java 复制代码
// 登录时同时返回两个Token
public LoginResponse login(String username, String password) {
    // 验证用户名密码
    String accessToken = jwtUtils.generateAccessToken(username, 30 * 60 * 1000);  // 30分钟
    String refreshToken = jwtUtils.generateRefreshToken(username, 7 * 24 * 60 * 60 * 1000);  // 7天
    return new LoginResponse(accessToken, refreshToken);
}

// 刷新接口
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestHeader("Refresh-Token") String refreshToken) {
    if (!jwtUtils.validateRefreshToken(refreshToken)) {
        return ResponseEntity.status(401).body("Refresh Token无效");
    }
    String username = jwtUtils.getUsernameFromRefreshToken(refreshToken);
    String newAccessToken = jwtUtils.generateAccessToken(username);
    return ResponseEntity.ok(new JwtResponse(newAccessToken));
}

💡 加分回答

更安全的做法:Refresh Token一次有效,使用后立即失效并颁发新的Refresh Token。同时用Redis记录Refresh Token,支持"踢人下线"和"多设备管理"。

注意:

面试官追问清单(防踩坑)⚡

坑1:"JWT的Payload是加密的吗?"

  • ❌ 错误:是加密的

  • ✅ 正确:不是加密,是Base64编码,任何人可解码查看。防篡改靠的是签名。

坑2:"Session和JWT哪个更安全?"

  • ❌ 错误:JWT更安全

  • ✅ 正确:安全性取决于实现。Session有CSRF风险,JWT有窃取风险。敏感业务用Session,一般业务用JWT

坑3:"Spring Security默认Session策略是什么?"

  • ✅ 正确:默认是IF_REQUIRED(需要时创建)。JWT场景必须改为STATELESS

坑4:"JWT过期后如何自动续签?"

  • ❌ 错误:自动续签

  • ✅ 正确:JWT无法自动续签。需要用Refresh Token滑动过期策略。

相关推荐
spencer_tseng3 小时前
Redis + Nacos.bat
java·windows·dos
troyzhxu4 小时前
列表查询的 GraphQL —— 一行代码终结你的 if-else 地狱!
java·springboot·graphql
孫治AllenSun4 小时前
【DataX】生产环境搭建DataX集群案例
java·开发语言·jvm
好好沉淀5 小时前
@NotBlank(message = “{xxx}“) 注解中花括号的含义
java
fīɡЙtīиɡ ℡6 小时前
内存泄漏产生的原因
java·spring·servlet
hkj88086 小时前
Ubuntu 切换java版本
java·linux·ubuntu
码智社7 小时前
Java实现RSA密钥生成、加密解密、加签验签
java
wear工程师7 小时前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端