Skip to content

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-secret
java
@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 委托给 ProviderManagerDaoAuthenticationProvider 调用 UserDetailsService.loadUserByUsername() 查用户 → PasswordEncoder.matches() 验密码 → 返回已认证 Token → 存入 SecurityContextHolder

Q2:JWT 和 Session 的区别?

维度JWTSession
存储位置客户端(浏览器)服务端(内存/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 Chain15 个过滤器顺序执行,各司其职
JWT无状态 Token,微服务认证首选
BCrypt自动加盐加密,安全性最高
@PreAuthorize方法级别权限控制,SpEL 表达式灵活
Gateway 认证统一入口认证,业务服务只做授权