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会默认注入这些核心组件。我们通常只需要关注UserDetailsService和PasswordEncoder的自定义实现。
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);
}
}
💡 加分回答:
关键点:
JWT过滤器要放在
UsernamePasswordAuthenticationFilter之前登录接口不需要JWT,要配置
permitAll()必须设置
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 或滑动过期策略。