Appearance
Spring Security 详解:认证与授权
Spring Security 是 Spring 生态中负责认证(Authentication)和授权(Authorization)的安全框架,是微服务安全体系的基石。它基于 Filter Chain 责任链模式,提供从表单登录到 OAuth2 分布式认证的全套能力。
一、为什么需要 Spring Security?
1.1 没有安全防护会怎样?
一个没有防护的微服务系统:
浏览器 ──→ Gateway ──→ order-service
│
├── 任何请求都能通过(无认证)
├── 任何用户都能查所有订单(无授权)
├── CSRF 攻击随意发起
└── 密码明文传输
只需要知道 URL,就能访问任意接口,毫无安全可言。1.2 Spring Security 能做什么?
| 能力 | 说明 |
|---|---|
| 认证 | 你是谁?密码登录、JWT Token、OAuth2 第三方登录 |
| 授权 | 你能做什么?角色权限、方法级别权限、URL 级别权限 |
| 攻击防护 | CSRF 防护、Session 固定攻击防护、点击劫持防护 |
| 密码加密 | BCrypt/SCrypt/PBKDF2 多种加密算法 |
| 分布式会话 | 无状态 JWT Token、Session 共享 |
1.3 Spring Security 在微服务中的位置
┌────────────────────────────────────────────────────────────────┐
│ 请求 → 认证 → 授权 → 业务 │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Gateway │→ │ Auth Service │→ │ 业务服务集群 │ │
│ │ │ │ │ │ │ │
│ │ 全局鉴权 │ │ 登录/注册 │ │ @PreAuthorize 切面鉴权 │ │
│ │ Token 校验│ │ Token 签发 │ │ 服务间调用认证 │ │
│ │ 路由守卫 │ │ 刷新 Token │ │ 数据权限校验 │ │
│ └──────────┘ └──────────────┘ └──────────────────────┘ │
└────────────────────────────────────────────────────────────────┘二、核心概念
2.1 认证 vs 授权
认证(Authentication): 授权(Authorization):
"你是谁?" "你能做什么?"
输入用户名密码 → 系统验证身份 通过认证后 → 检查你的权限
类比: 类比:
进入公司大楼,刷工卡验证身份 进入后,不同区域需要不同权限
"你是张三" "张三能进研发部,不能进财务部"2.2 Filter Chain(过滤器链)
Spring Security 的核心是一套过滤器链(Filter Chain),每个请求都要经过所有过滤器:
请求 → Filter 1 → Filter 2 → ... → Filter N → DispatcherServlet → Controller
Spring Security 默认的 15 个过滤器(按顺序):
1. WebAsyncManagerIntegrationFilter ← 异步线程 SecurityContext 传递
2. SecurityContextPersistenceFilter ← 从 Session 恢复 SecurityContext
3. HeaderWriterFilter ← 添加安全响应头(X-XSS-Protection 等)
4. CsrfFilter ← CSRF 攻击防护
5. LogoutFilter ← 处理登出
6. UsernamePasswordAuthenticationFilter ← 表单登录认证 ★
7. DefaultLoginPageGeneratingFilter ← 生成默认登录页
8. BasicAuthenticationFilter ← HTTP Basic 认证
9. RequestCacheAwareFilter ← 恢复被中断的请求
10. SecurityContextHolderAwareRequestFilter ← 包装请求
11. AnonymousAuthenticationFilter ← 未认证用户给匿名身份
12. SessionManagementFilter ← 会话管理
13. ExceptionTranslationFilter ← 异常转换(403 → 登录页)
14. FilterSecurityInterceptor ← 授权决策 ★
15. BearerTokenAuthenticationFilter ← JWT Bearer Token 认证 ★关键理解: 前 14 个过滤器做认证,最后一个 FilterSecurityInterceptor 做授权。认证通过后,授权过滤器才决定你能不能访问当前资源。
2.3 SecurityContext(安全上下文)
SecurityContextHolder(持有者,ThreadLocal 存储)
│
▼
SecurityContext(上下文)
│
▼
Authentication(认证信息)
├── Principal(主体,通常是 UserDetails)
├── Credentials(凭证,密码/Token,认证后清空)
└── Authorities(权限列表,如 ROLE_ADMIN, ROLE_USER)获取当前用户:
java
// 方式一:SecurityContextHolder(任何地方都能用)
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
boolean isAdmin = auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
// 方式二:Controller 注入(推荐)
@GetMapping("/profile")
public User profile(@AuthenticationPrincipal UserDetails user) {
return userService.findByUsername(user.getUsername());
}
// 方式三:直接注入 Authentication
@GetMapping("/me")
public String me(Authentication auth) {
return auth.getName();
}2.4 AuthenticationManager
认证管理器,负责"验证你是谁"。默认实现是 ProviderManager,它管理一组 AuthenticationProvider:
ProviderManager
├── DaoAuthenticationProvider ← 从数据库查用户,校验密码
├── JwtAuthenticationProvider ← 从 JWT Token 解析用户
├── OAuth2LoginAuthenticationProvider ← OAuth2 第三方登录
└── 自定义 Provider认证流程:
1. 用户提交用户名密码
│
2. UsernamePasswordAuthenticationFilter 创建 Authentication(未认证)
│
3. ProviderManager 遍历所有 AuthenticationProvider
│
4. DaoAuthenticationProvider 匹配成功
│
5. UserDetailsService.loadUserByUsername() 加载用户信息
│
6. PasswordEncoder.matches() 校验密码
│
7. 返回 Authentication(已认证),放入 SecurityContextHolder三、快速上手
3.1 依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>3.2 第一个安全配置
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 授权规则
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/login", "/error").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
// 表单登录
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/home")
.permitAll()
)
// 登出
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
// 内存用户(仅演示,生产环境用数据库)
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN")
.build();
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("123456"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}四、认证方式详解
4.1 数据库认证
java
// 实体类
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
private Long id;
private String username;
private String password;
private boolean enabled;
@ElementCollection(fetch = FetchType.EAGER)
private Set<String> roles = new HashSet<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
}
// UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
}
}
// 配置
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}4.2 JWT 无状态认证(微服务核心)
微服务集群中,传统 Session 需要共享存储。JWT(JSON Web Token)的无状态特性使其成为微服务认证的首选。
JWT 结构(三段式,Base64 编码):
Header.Payload.Signature
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "1001", "name": "张三", "exp": 1700000000}
Signature: HMACSHA256(Header + "." + Payload, secret)
优点: 风险:
✅ 无状态,不需要服务端存储 Session ⚠️ Token 一旦签发,服务端无法主动失效
✅ 跨语言,JSON 格式通用 ⚠️ Payload 仅 Base64 编码,不要放敏感信息
✅ 适合分布式,每个服务都能独立校验 ⚠️ Token 比 Session ID 大,增加请求体积完整 JWT 认证流程:
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ 用户 │ │ Auth Service │ │ 业务服务 │
│(浏览器) │ │ (登录/签发) │ │ (校验 Token) │
└────┬─────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 1. POST /login │ │
│ 用户名+密码 │ │
│─────────────────→│ │
│ │ 2. 查数据库校验 │
│ │ 3. 生成 AccessToken │
│ │ + RefreshToken │
│ 4. 返回 Token │ │
│←─────────────────│ │
│ │ │
│ 5. GET /orders │ │
│ Authorization: Bearer xxx │
│──────────────────────────────────────→│
│ │ │ 6. 解析 Token
│ │ │ 7. 获取用户信息
│ │ │ 8. 执行业务逻辑
│ 9. 返回数据 │ │
│←──────────────────────────────────────│代码实现:
java
// ============ JWT 工具类 ============
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration; // 毫秒
// 生成 Token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", ((User) userDetails).getId());
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
.compact();
}
// 从 Token 中提取用户名
public String getUsernameFromToken(String token) {
return getClaimsFromToken(token).getSubject();
}
// 校验 Token
public boolean validateToken(String token) {
try {
getClaimsFromToken(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
private Claims getClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
.build()
.parseClaimsJws(token)
.getBody();
}
}
// ============ JWT 认证过滤器 ============
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
String username = jwtUtils.getUsernameFromToken(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtils.validateToken(token)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
// ============ 登录接口 ============
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
// 认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成 Token
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
String accessToken = jwtUtils.generateToken(userDetails);
return ResponseEntity.ok(new LoginResponse(accessToken));
}
}
// ============ 配置 ============
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // JWT 不需要 CSRF
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}4.3 OAuth2 第三方登录
yaml
spring:
security:
oauth2:
client:
registration:
github:
client-id: your-client-id
client-secret: your-client-secret
google:
client-id: your-client-id
client-secret: your-client-secretjava
@Configuration
@EnableWebSecurity
public class OAuth2Config {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/home")
);
return http.build();
}
}五、授权方式详解
5.1 URL 级别授权
java
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 公开访问
.requestMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 或 ADMIN
.requestMatchers(HttpMethod.DELETE, "/**").hasRole("ADMIN") // DELETE 只有 ADMIN
.requestMatchers("/api/**").hasAuthority("api:read") // 需要特定权限
.anyRequest().authenticated() // 其余需要登录
);5.2 方法级别授权
java
@Configuration
@EnableMethodSecurity // 开启方法级别安全
public class MethodSecurityConfig {}
// 使用
@Service
public class OrderService {
@PreAuthorize("hasRole('ADMIN')")
public List<Order> getAllOrders() {
return orderRepository.findAll();
}
@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public Order getOrderById(Long userId, Long orderId) {
return orderRepository.findById(orderId).orElse(null);
}
@PreAuthorize("hasAuthority('order:create')")
public Order createOrder(OrderDTO dto) {
// ...
}
@PostFilter("filterObject.userId == authentication.principal.id")
public List<Order> getMyOrders() {
return orderRepository.findAll(); // 返回结果自动过滤
}
}常用 SpEL 表达式:
| 表达式 | 含义 |
|---|---|
hasRole('ADMIN') | 有 ADMIN 角色 |
hasAnyRole('ADMIN', 'USER') | 有 ADMIN 或 USER 角色 |
hasAuthority('order:create') | 有 order:create 权限 |
#id == authentication.principal.id | 参数 id 等于当前用户 id |
@orderService.isOwner(#orderId) | 调用 Bean 方法判断 |
5.3 RBAC 权限模型
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户 │────→│ 角色 │────→│ 权限 │
│ (User) │ 多对多│ (Role) │ 多对多│(Permission)│
└──────────┘ └──────────┘ └──────────┘
示例:
张三 ──→ 管理员 ──→ 订单管理、用户管理、系统设置
李四 ──→ 普通用户 ──→ 订单查询、个人信息
王五 ──→ 运营 ──→ 订单管理、商品管理数据库设计:
sql
-- 5 张表实现 RBAC
CREATE TABLE users (
id BIGINT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password VARCHAR(255)
);
CREATE TABLE roles (
id BIGINT PRIMARY KEY,
name VARCHAR(50) UNIQUE
);
CREATE TABLE permissions (
id BIGINT PRIMARY KEY,
name VARCHAR(100) UNIQUE -- 如 "order:create", "order:delete"
);
CREATE TABLE user_roles (
user_id BIGINT,
role_id BIGINT,
PRIMARY KEY (user_id, role_id)
);
CREATE TABLE role_permissions (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id)
);六、微服务安全架构
6.1 认证中心 + 资源服务器
架构设计(认证与资源分离):
┌─────────────────────────────────────────────────────────────────┐
│ 微服务安全架构 │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Gateway │→ │ Auth Service │→ │ 业务服务集群 │ │
│ │ │ │ (认证中心) │ │ │ │
│ │ JWT 校验 │ │ 登录/注册 │ │ @PreAuthorize 鉴权 │ │
│ │ 路由转发 │ │ Token 签发 │ │ 资源级别权限控制 │ │
│ └──────────┘ │ Token 刷新 │ └──────────────────────┘ │
│ └──────────────┘ │
│ │
│ 认证流程: │
│ 1. 用户请求 /login → Auth Service 签发 JWT │
│ 2. 用户携带 JWT 请求业务 → Gateway 校验 JWT 有效性 │
│ 3. Gateway 转发 → 业务服务 @PreAuthorize 校验权限 │
│ │
│ ★ Gateway 只做"你是谁"(认证),业务服务做"你能做什么"(授权) │
└──────────────────────────────────────────────────────────────────┘6.2 Gateway 统一认证
java
@Configuration
public class GatewayAuthConfig {
@Bean
public GlobalFilter authFilter(JwtUtils jwtUtils) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单路径直接放行
if (path.startsWith("/login") || path.startsWith("/public")) {
return chain.filter(exchange);
}
String authHeader = request.getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return unauthorized(exchange, "缺少认证 Token");
}
String token = authHeader.substring(7);
if (!jwtUtils.validateToken(token)) {
return unauthorized(exchange, "Token 无效或已过期");
}
// 将用户信息放入请求头,传递给下游服务
String username = jwtUtils.getUsernameFromToken(token);
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-Id", jwtUtils.getUserIdFromToken(token))
.header("X-Username", username)
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
};
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":401,\"message\":\"" + message + "\"}";
return exchange.getResponse()
.writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(body.getBytes())));
}
}6.3 服务间调用认证
java
// Feign 拦截器:自动携带 Token
@Configuration
public class FeignAuthConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getCredentials() instanceof String) {
requestTemplate.header("Authorization", "Bearer " + auth.getCredentials());
}
};
}
}七、密码安全
7.1 BCrypt 加密
java
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 使用
String encoded = passwordEncoder.encode("123456");
// 每次结果不同:$2a$10$N9qo8uLOickgx2ZMRZoMye...
boolean matches = passwordEncoder.matches("123456", encoded);
// true
// BCrypt 特点:
// ✅ 自动加盐,每次加密结果不同
// ✅ 可以调节计算强度(cost factor,默认 10)
// ✅ 彩虹表攻击无效
// ✅ 慢哈希,暴力破解成本极高7.2 DelegatingPasswordEncoder(密码升级)
java
@Bean
public PasswordEncoder delegatingPasswordEncoder() {
String idForEncode = "bcrypt";
Map<String, PasswordEncoder> encoders = Map.of(
"bcrypt", new BCryptPasswordEncoder(),
"scrypt", new SCryptPasswordEncoder(
16384, 8, 1, 32, 64),
"pbkdf2", new Pbkdf2PasswordEncoder(
"secret", 185000, 256)
);
return new DelegatingPasswordEncoder(idForEncode, encoders);
}
// 存储格式:{bcrypt}$2a$10$... 或 {pbkdf2}...
// 可以根据前缀自动选择解密算法,方便密码算法升级八、常见漏洞与防护
| 漏洞 | 说明 | Spring Security 防护 |
|---|---|---|
| CSRF | 跨站请求伪造,诱导用户点击恶意链接 | 默认开启 CSRF Token(非 API 场景) |
| XSS | 跨站脚本注入 | Content-Security-Policy 响应头 |
| Session 固定 | 攻击者固定 Session ID 劫持会话 | sessionManagement().sessionFixation().migrateSession() |
| 点击劫持 | 透明 iframe 覆盖诱导点击 | X-Frame-Options: DENY 响应头 |
| 暴力破解 | 穷举密码 | 限流 + 账户锁定 + 验证码 |
| 密码明文传输 | HTTP 明文传输密码 | 强制 HTTPS |
九、面试要点
Q1:Spring Security 的认证流程?
请求 → UsernamePasswordAuthenticationFilter 创建未认证 Token → AuthenticationManager 委托给 ProviderManager → DaoAuthenticationProvider 调用 UserDetailsService.loadUserByUsername() 查用户 → PasswordEncoder.matches() 验密码 → 返回已认证 Token → 存入 SecurityContextHolder。
Q2:JWT 和 Session 的区别?
| 维度 | JWT | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务端(内存/Redis) |
| 伸缩性 | 天然支持分布式 | 需要 Session 共享 |
| 注销 | 无法主动失效 | 服务端删除即失效 |
| 体积 | 较大(几百字节) | 极小(Session ID) |
| 适合场景 | 微服务集群、移动端 | 传统单体应用 |
Q3:为什么 JWT 不需要 CSRF 防护?
CSRF 攻击依赖浏览器自动携带 Cookie。JWT 存储在 localStorage 或内存中,需要手动添加到请求头,浏览器不会自动携带,因此 CSRF 攻击无效。
Q4:BCrypt 为什么每次加密结果不同?
BCrypt 内置随机盐(Salt),每次加密生成不同的盐值,附加到密文中。即使相同密码,每次加密结果也不同。验证时从密文中提取盐值重新计算。
Q5:如何实现 Token 自动续期?
双 Token 机制:AccessToken(短期,15 分钟)+ RefreshToken(长期,7 天)。AccessToken 过期时,用 RefreshToken 换取新 Token。RefreshToken 也过期则需重新登录。
Q6:OAuth2 有哪四种授权模式?
| 模式 | 说明 | 场景 |
|---|---|---|
| 授权码模式 | 先获取 code,再换 token | 有后端的 Web 应用 |
| 简化模式 | 直接返回 token | 纯前端 SPA(已废弃) |
| 密码模式 | 用户名密码直接换 token | 信任的客户端(已废弃) |
| 客户端模式 | 客户端 ID + Secret 换 token | 服务间调用 |
当前推荐使用授权码模式 + PKCE,安全且通用。
Q7:如何处理"记住我"功能?
java
http.rememberMe(remember -> remember
.key("uniqueAndSecret")
.tokenValiditySeconds(86400 * 7) // 7 天
.userDetailsService(userDetailsService())
);十、总结
| 核心概念 | 一句话 |
|---|---|
| 认证 | 你是谁?通过密码/Token 验证身份 |
| 授权 | 你能做什么?通过角色/权限控制访问 |
| Filter Chain | 15 个过滤器顺序执行,各司其职 |
| JWT | 无状态 Token,微服务认证首选 |
| BCrypt | 自动加盐加密,安全性最高 |
| @PreAuthorize | 方法级别权限控制,SpEL 表达式灵活 |
| Gateway 认证 | 统一入口认证,业务服务只做授权 |
