DukeDuke
主页
项目文档
技术文档
  • 单机版
  • 微服务
  • 代办项目
  • 优鲜项目
项目管理
关于我们
主页
项目文档
技术文档
  • 单机版
  • 微服务
  • 代办项目
  • 优鲜项目
项目管理
关于我们
  • 技术文档

    • 网络原理

      • 交换机
      • 路由器
      • TCP/IP协议
      • HTTP 与 HTTPS
    • 软件架构

      • 什么是软件架构
      • 分层架构
      • 微服务架构
      • 事件驱动架构
      • 领域驱动设计(DDD)
      • 架构图
      • 高并发系统
    • Vue3

      • Vue3简介
      • Vue3响应式系统
      • Vue3组合式API
      • Vue3生命周期
      • Vue3模板语法
      • Vue3组件系统
      • Vue3 路由系统
      • Vue3 状态管理
      • Vue3 性能优化
      • Vue3 TypeScript 支持
      • Vue3 项目实战
      • VUE 面试题大全
      • Node.js 安装
    • JAVA

      • JVM

        • 认识JVM
        • JVM类加载器
        • 运行时数据区
        • 执行引擎
        • 本地方法接口
        • 本地方法库
        • JVM垃圾回收
        • JVM性能监控
        • JVM调优
      • 设计模式
        • 单例模式
        • 工厂模式
        • 策略模式
        • 适配器模式
        • 建造者模式
        • 原型模式
        • 装饰器模式
        • 代理模式
        • 外观模式
        • 享元模式
        • 组合模式
        • 桥接模式
      • Java多线程

        • Java 线程基础详解
        • Java 线程池详解
        • Java ThreadLocal 详解
        • Java volatile 详解
        • Java 线程间通信详解
        • Java 线程安全详解
        • Java 线程调度详解
        • Java 线程优先级详解

        • Java 线程中断详解
        • Java 线程死锁详解
      • Java反射
      • Java 面试题

        • Java 基础概念面试题
        • Java 面向对象编程面试题
        • Java 集合框架面试题
        • Java 多线程与并发面试题
        • JVM 与内存管理面试题
        • Java I/O 与 NIO 面试题
        • Java 异常处理面试题
        • Java 反射与注解面试题
        • Java Spring 框架面试题
        • Java 数据库与 JDBC 面试题
        • Java 性能优化面试题
        • Java 实际项目经验面试题
        • Java 高级特性面试题
        • Java 面试准备建议
    • Python

      • Python简介
      • Python安装
      • Python hello world
      • Python基础语法
      • Python数据类型
      • Python数字
      • Python字符串
      • Python列表
      • Python元组
      • Python字典
      • Python日期时间
      • Python文件操作
      • Python异常处理
      • Python函数
      • Python类
      • Python模块
      • Python包
      • Python多线程
      • Python面向对象
      • Python爬虫
      • Django web框架
      • Python 面试题

        • Python 面试题导航
        • Python 基础概念
        • Python 面向对象编程
        • Python 数据结构
        • Python 高级特性
        • Python 框架
        • Python 性能优化
        • Python 项目经验
    • Spring

      • Spring
      • Springboot
      • Spring Security 安全框架
      • SpringBoot 中的事件详解
      • SpringBoot 中的定时任务详解
      • SpringBoot 自动装配原理与源码解释
    • Mybatis

      • Mybatis
      • Mybatis-Plus
    • 数据库

      • Redis

        • Redis简介
        • Redis(单机)安装
        • Redis配置
        • Redis数据结构
        • RDB、AOF 和混合持久化机制
        • Redis内存管理
        • Redis缓存一致性
        • Redis缓存穿透
        • Redis缓存击穿
        • Redis缓存雪崩
        • Redis Lua脚本
        • Redis主从复制
        • Redis哨兵模式
        • Redis集群
        • Redis数据分片
        • Redis CPU使用率过高
        • Redis面试题
      • MySQL

        • MySQL简介
        • MySQL安装
        • MySQL配置
        • MYSQL日常维护
        • MYSQL优化-慢查询
        • MYSQL优化-索引
        • MYSQL数据库设计规范
    • 消息队列

      • RocketMQ
      • Kafka
      • RabbitMQ
      • 消息队列面试题
    • 微服务

      • SpringCloud 微服务
      • Eureka 注册中心
      • Nacos 注册中心
      • Gateway 网关
      • Feign 服务调用
      • Sentinel 限流 与 熔断
      • Seata 分布式事务
      • CAP 理论
      • Redis 分布式锁
      • 高并发系统设计
    • ELK日志分析系统

      • Elasticsearch 搜索引擎
      • Logstash 数据处理
      • Kibana 可视化
      • ELK 实战
    • 开放API

      • 开放API设计
      • 开放API示例项目
    • 人工智能

      • 人工智能简介
      • 机器学习

      • 深度学习

      • 自然语言处理

      • 计算机视觉

        • CUDA与cuDNN详细安装
        • Conda 安装
        • Pytorch 深度学习框架
        • yolo 目标检测
        • TensorRT 深度学习推理优化引擎
        • TensorFlow 机器学习
        • CVAT 图像标注
        • Windows 下安装 CUDA、cuDNN、TensorRT、TensorRT-YOLO 环境
        • Windows10+CUDA+cuDNN+TensorRT+TensorRT-YOLO 部署高性能YOLO11推理
    • 大数据

      • 大数据简介
      • Hadoop 数据存储
      • Flume 数据采集
      • Sqoop 数据导入导出
      • Hive 数据仓库
      • Spark 数据处理
      • Flink 数据处理
      • Kafka 数据采集
      • HBase 数据存储
      • Elasticsearch 搜索引擎
    • 图像处理

      • 图像处理简介
      • 医学图像web呈现
      • 医学图像处理
      • 切片细胞分离问题
    • 服务器&运维

      • Linux 系统

        • Linux 系统管理
        • Linux 网络管理
        • Linux 文件管理
        • Linux 命令大全
      • Nginx Web 服务器

        • Nginx 安装 与 配置
        • Nginx 负载均衡
        • Nginx SSL证书配置
        • Nginx Keepalived 高可用
      • Docker 容器

        • Docker 简介
        • Docker 安装与配置
        • Docker 命令
        • Docker 部署 Nginx
        • Docker 部署 MySQL
        • Docker 部署 Redis
      • 服务器

        • 塔式服务器
        • 机架式服务器
        • 刀片服务器
      • Git 版本控制
      • Jenkins 持续集成
      • Jmeter 性能测试
      • Let's Encrypt 免费SSL证书
    • 简历

      • 项目经理简历
      • 开发工程师简历

Spring Security 安全框架详解

1. 概述

1.1 什么是 Spring Security

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是 Spring 生态系统中的安全标准,为基于 Spring 的应用程序提供全面的安全解决方案。

主要特性:

  • 🔐 身份认证(Authentication)
  • 🛡️ 授权控制(Authorization)
  • 🔒 会话管理(Session Management)
  • 🛠️ 攻击防护(Attack Prevention)
  • 🔧 高度可定制(Highly Customizable)

2. 核心概念

2.1 安全上下文(SecurityContext)

SecurityContext 是 Spring Security 的核心概念,它包含了当前用户的认证信息。

// SecurityContext结构
public interface SecurityContext {
    Authentication getAuthentication();
    void setAuthentication(Authentication authentication);
}

2.2 认证(Authentication)

认证是验证用户身份的过程,确认"你是谁"。

public interface Authentication {
    // 用户主体信息
    Object getPrincipal();

    // 用户凭证
    Object getCredentials();

    // 用户权限
    Collection<? extends GrantedAuthority> getAuthorities();

    // 认证详情
    Object getDetails();

    // 是否已认证
    boolean isAuthenticated();

    // 设置认证状态
    void setAuthenticated(boolean isAuthenticated);
}

2.3 授权(Authorization)

授权是控制用户访问权限的过程,确认"你能做什么"。

// 权限接口
public interface GrantedAuthority {
    String getAuthority();
}

// 角色接口
public interface Role extends GrantedAuthority {
    String getRoleName();
}

2.4 用户详情(UserDetails)

UserDetails 接口定义了用户的核心信息。

public interface UserDetails {
    String getUsername();
    String getPassword();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
    Collection<? extends GrantedAuthority> getAuthorities();
}

3. 架构原理

3.1 整体架构

Spring Security 的整体架构采用了分层设计模式,构建了一个完整的安全防护体系。该架构从客户端层开始,经过多个安全处理层,最终到达用户存储和访问决策层,形成了一个完整的安全处理链路。

客户端层是架构的入口点,包括 Web 浏览器、移动应用和 API 客户端等多种类型的客户端。这些客户端通过 HTTP 请求与应用程序进行交互,每个请求都会经过 Spring Security 的安全处理流程。

Spring Security 过滤器链是安全处理的核心组件,它由多个专门的过滤器组成。SecurityFilterChain 作为过滤器链的容器,协调各个过滤器的执行顺序。AuthenticationFilter 负责处理认证相关的逻辑,提取和验证用户的身份信息。AuthorizationFilter 负责处理授权逻辑,判断用户是否有权限访问特定资源。SessionManagementFilter 则负责会话管理,包括会话的创建、维护和销毁。

认证管理器是认证流程的控制中心,AuthenticationManager 作为认证管理器的主要接口,定义了认证的标准流程。ProviderManager 是 AuthenticationManager 的具体实现,它管理多个 AuthenticationProvider 实例。AuthenticationProvider 负责具体的认证逻辑,可以支持多种认证方式,如用户名密码认证、OAuth2 认证等。

用户存储层提供了用户信息的持久化方案。UserDetailsService 是用户信息服务的核心接口,负责加载用户的详细信息。用户信息可以存储在不同的数据源中,包括关系型数据库、LDAP 目录服务和内存存储等。这种设计使得系统可以灵活地选择用户存储方案,适应不同的部署环境。

访问决策层负责最终的权限判断。AccessDecisionManager 是访问决策管理器,它协调多个 Voter(投票器)来做出最终的访问决策。RoleVoter 是基于角色的投票器,根据用户的角色来判断是否有访问权限。这种投票机制使得权限判断更加灵活和可扩展。

整个架构通过清晰的层次划分和组件职责分离,实现了安全功能的模块化和可扩展性。每个层次都有明确的职责,通过标准化的接口进行交互,使得系统既保证了安全性,又具备了良好的可维护性和可扩展性。

3.2 过滤器链原理

Spring Security 基于 Servlet 过滤器链实现安全控制:

// 过滤器链配置示例
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")
            );
        return http.build();
    }
}

3.3 认证流程

Spring Security 的认证流程是一个精心设计的交互过程,它确保了用户身份验证的安全性和可靠性。整个流程从客户端发起请求开始,经过多个组件的协作处理,最终完成用户的身份认证。

客户端发起请求是认证流程的起点。当用户尝试访问受保护的资源时,客户端会向服务器发送包含认证信息的请求。这些认证信息可能是用户名和密码、JWT 令牌、OAuth2 令牌等多种形式。

SecurityFilter 提取认证信息是流程的第一个处理步骤。过滤器会从请求中提取认证相关的信息,如 HTTP 头中的 Authorization 字段、Cookie 中的会话标识符等。提取的信息会被封装成 Authentication 对象,为后续的认证处理做准备。

AuthenticationManager 创建认证对象是认证流程的核心步骤。认证管理器接收从过滤器传来的认证信息,创建一个标准的 Authentication 对象。这个对象包含了用户的身份信息、凭证信息以及相关的元数据。

AuthenticationProvider 执行认证是实际进行身份验证的环节。认证提供者接收 Authentication 对象,根据配置的认证策略进行身份验证。它可能会调用 UserDetailsService 来加载用户的详细信息,然后验证用户提供的凭证是否正确。

UserDetailsService 加载用户信息是获取用户详细信息的步骤。用户详情服务根据用户名或其他标识符从数据源中加载用户的完整信息,包括密码哈希、权限列表、账户状态等。这些信息对于后续的密码验证和权限判断至关重要。

密码验证和认证结果返回是认证流程的关键步骤。认证提供者将用户输入的密码与数据库中存储的密码哈希进行比对。如果验证成功,认证提供者会返回一个包含用户权限信息的完整 Authentication 对象。如果验证失败,则会抛出相应的异常。

SecurityContext 设置和响应返回是认证流程的最后步骤。认证成功后,用户的认证信息会被设置到 SecurityContext 中,这样后续的请求处理就可以通过 SecurityContext 获取到当前用户的身份和权限信息。最后,过滤器将处理结果返回给客户端,完成整个认证流程。

这个认证流程设计确保了认证过程的安全性和可靠性,通过多个组件的协作,实现了对用户身份的准确验证和权限的有效管理。

4. 认证机制

4.1 表单登录认证

@Configuration
@EnableWebSecurity
public class FormLoginConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .formLogin(form -> form
                .loginPage("/login")           // 自定义登录页
                .loginProcessingUrl("/auth")    // 登录处理URL
                .defaultSuccessUrl("/dashboard") // 登录成功跳转
                .failureUrl("/login?error")    // 登录失败跳转
                .usernameParameter("username")  // 用户名参数
                .passwordParameter("password")  // 密码参数
                .permitAll()
            );
        return http.build();
    }
}

4.2 HTTP Basic 认证

@Configuration
@EnableWebSecurity
public class BasicAuthConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .httpBasic(basic -> basic
                .realmName("My Application")
            )
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            );
        return http.build();
    }
}

4.3 JWT 认证

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                String userId = tokenProvider.getUserIdFromJWT(jwt);
                UserDetails userDetails = customUserDetailsService.loadUserById(userId);

                UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

4.4 OAuth2 认证

@Configuration
@EnableWebSecurity
public class OAuth2Config {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .failureUrl("/login?error")
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(customOAuth2UserService)
                )
            );
        return http.build();
    }
}

5. 授权机制

5.1 基于角色的访问控制(RBAC)

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class RoleBasedConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/user/**").hasRole("USER")
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            );
        return http.build();
    }
}

5.2 基于注解的授权

@RestController
@RequestMapping("/api")
public class UserController {

    // 需要USER角色
    @PreAuthorize("hasRole('USER')")
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getProfile() {
        return ResponseEntity.ok(userService.getCurrentUserProfile());
    }

    // 需要ADMIN角色
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/users")
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }

    // 自定义权限表达式
    @PreAuthorize("hasAuthority('USER_DELETE') and #id != authentication.principal.id")
    @DeleteMapping("/users/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.ok().build();
    }

    // 基于方法参数的权限控制
    @PreAuthorize("#user.id == authentication.principal.id or hasRole('ADMIN')")
    @PutMapping("/users/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        return ResponseEntity.ok(userService.updateUser(user));
    }
}

5.3 自定义权限评估器

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
            return false;
        }

        String targetType = targetDomainObject.getClass().getSimpleName().toUpperCase();
        return hasPrivilege(authentication, targetType, permission.toString().toUpperCase());
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        if (authentication == null || targetType == null || !(permission instanceof String)) {
            return false;
        }
        return hasPrivilege(authentication, targetType.toUpperCase(), permission.toString().toUpperCase());
    }

    private boolean hasPrivilege(Authentication auth, String targetType, String permission) {
        for (GrantedAuthority grantedAuth : auth.getAuthorities()) {
            if (grantedAuth.getAuthority().startsWith(targetType) &&
                grantedAuth.getAuthority().contains(permission)) {
                return true;
            }
        }
        return false;
    }
}

6. 会话管理

6.1 会话配置

@Configuration
@EnableWebSecurity
public class SessionConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .maximumSessions(1)                    // 最大会话数
                .expiredUrl("/login?expired")          // 会话过期跳转
                .and()
                .sessionFixation().migrateSession()     // 会话固定攻击防护
                .and()
                .invalidSessionUrl("/login?invalid")    // 无效会话跳转
            );
        return http.build();
    }
}

6.2 并发会话控制

@Component
public class CustomSessionRegistry implements SessionRegistry {

    private final Map<String, List<SessionInformation>> sessionMap = new ConcurrentHashMap<>();

    @Override
    public List<Object> getAllPrincipals() {
        return new ArrayList<>(sessionMap.keySet());
    }

    @Override
    public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {
        List<SessionInformation> sessions = sessionMap.get(principal.toString());
        if (sessions == null) {
            return new ArrayList<>();
        }

        if (includeExpiredSessions) {
            return new ArrayList<>(sessions);
        }

        return sessions.stream()
                .filter(session -> !session.isExpired())
                .collect(Collectors.toList());
    }

    @Override
    public SessionInformation getSessionInformation(String sessionId) {
        return sessionMap.values().stream()
                .flatMap(List::stream)
                .filter(session -> session.getSessionId().equals(sessionId))
                .findFirst()
                .orElse(null);
    }

    @Override
    public void refreshLastRequest(String sessionId) {
        SessionInformation session = getSessionInformation(sessionId);
        if (session != null) {
            session.refreshLastRequest();
        }
    }

    @Override
    public void registerNewSession(String sessionId, Object principal) {
        SessionInformation session = new SessionInformation(principal, sessionId, new Date());
        sessionMap.computeIfAbsent(principal.toString(), k -> new ArrayList<>()).add(session);
    }

    @Override
    public void removeSessionInformation(String sessionId) {
        sessionMap.values().forEach(sessions ->
            sessions.removeIf(session -> session.getSessionId().equals(sessionId))
        );
    }
}

7. 密码加密

7.1 密码编码器

@Configuration
public class PasswordEncoderConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12); // 强度为12
    }

    // 多种编码器支持
    @Bean
    public DelegatingPasswordEncoder delegatingPasswordEncoder() {
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());

        return new DelegatingPasswordEncoder("bcrypt", encoders);
    }
}

7.2 密码验证流程

Spring Security 的密码验证流程是一个多步骤的安全处理过程,它确保了用户密码的安全性和验证的准确性。整个流程从用户输入密码开始,经过多个安全处理环节,最终完成密码的验证。

用户输入密码是密码验证流程的起点。用户在登录界面输入用户名和密码,这些信息会被前端收集并准备发送到服务器。为了确保传输安全,前端可能会对密码进行初步的加密处理,如使用 HTTPS 传输或进行客户端加密。

前端加密处理是密码安全传输的重要环节。前端可能会对用户输入的密码进行加密处理,如使用 JavaScript 加密库对密码进行哈希处理,或者使用 HTTPS 协议确保传输过程中的安全性。这一步的目的是防止密码在传输过程中被窃取。

传输到服务器是密码信息的安全传输过程。加密后的密码信息通过安全的网络连接传输到服务器端。服务器接收这些信息后,会进行进一步的验证和处理。

Spring Security 接收处理是服务器端的安全处理环节。Spring Security 接收到包含密码信息的请求后,会通过配置的认证过滤器提取密码信息,并将其传递给相应的认证提供者进行处理。

PasswordEncoder 验证是密码验证的核心步骤。Spring Security 使用配置的密码编码器来验证用户输入的密码。密码编码器会将用户输入的原始密码与数据库中存储的加密密码进行比对,判断密码是否正确。

密码匹配判断是验证结果的决策环节。如果用户输入的密码与数据库中存储的密码匹配,则认证成功,用户可以继续访问受保护的资源。如果密码不匹配,则认证失败,系统会返回相应的错误信息。

认证结果处理是流程的最后步骤。根据密码验证的结果,系统会执行相应的后续处理。认证成功时,系统会创建用户的认证会话,并允许用户访问受保护的资源。认证失败时,系统会记录失败信息,并可能触发安全策略,如账户锁定或登录尝试限制。

这个密码验证流程设计确保了密码验证的安全性和可靠性,通过多层的安全处理,有效防止了密码泄露和未授权访问的风险。

7.3 自定义密码编码器

@Component
public class CustomPasswordEncoder implements PasswordEncoder {

    private static final String SALT = "MyCustomSalt";

    @Override
    public String encode(CharSequence rawPassword) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            String saltedPassword = rawPassword + SALT;
            byte[] hashedBytes = md.digest(saltedPassword.getBytes());
            return Base64.getEncoder().encodeToString(hashedBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Error encoding password", e);
        }
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        String encodedRawPassword = encode(rawPassword);
        return encodedRawPassword.equals(encodedPassword);
    }
}

8. 实战示例

8.1 完整的 Spring Security 配置

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/user/**").hasRole("USER")
                .anyRequest().authenticated()
            )
            .exceptionHandling(exceptions -> exceptions
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

8.2 用户实体和 DTO

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String email;

    @Enumerated(EnumType.STRING)
    private UserStatus status = UserStatus.ACTIVE;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();

    // getters and setters
}

@Entity
@Table(name = "roles")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    @Column(unique = true, nullable = false)
    private RoleName name;

    // getters and setters
}

public enum RoleName {
    ROLE_USER,
    ROLE_ADMIN,
    ROLE_MODERATOR
}

public enum UserStatus {
    ACTIVE,
    INACTIVE,
    LOCKED
}

8.3 认证控制器

@RestController
@RequestMapping("/api/auth")
@Validated
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @PostMapping("/login")
    public ResponseEntity<JwtAuthenticationResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsername(),
                loginRequest.getPassword()
            )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtTokenProvider.generateToken(authentication);

        return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
    }

    @PostMapping("/register")
    public ResponseEntity<ApiResponse> registerUser(@Valid @RequestBody SignUpRequest signUpRequest) {
        if (userRepository.existsByUsername(signUpRequest.getUsername())) {
            return ResponseEntity.badRequest()
                .body(new ApiResponse(false, "用户名已存在"));
        }

        if (userRepository.existsByEmail(signUpRequest.getEmail())) {
            return ResponseEntity.badRequest()
                .body(new ApiResponse(false, "邮箱已存在"));
        }

        User user = new User();
        user.setUsername(signUpRequest.getUsername());
        user.setEmail(signUpRequest.getEmail());
        user.setPassword(passwordEncoder.encode(signUpRequest.getPassword()));

        Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
            .orElseThrow(() -> new RuntimeException("用户角色未找到"));
        user.setRoles(Collections.singleton(userRole));

        userRepository.save(user);

        return ResponseEntity.ok(new ApiResponse(true, "用户注册成功"));
    }

    @GetMapping("/me")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<UserSummary> getCurrentUser(@CurrentUser UserPrincipal currentUser) {
        UserSummary userSummary = new UserSummary(
            currentUser.getId(),
            currentUser.getUsername(),
            currentUser.getEmail()
        );
        return ResponseEntity.ok(userSummary);
    }
}

8.4 JWT 工具类

@Component
public class JwtTokenProvider {

    @Value("${app.jwtSecret}")
    private String jwtSecret;

    @Value("${app.jwtExpirationInMs}")
    private int jwtExpirationInMs;

    public String generateToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

        return Jwts.builder()
                .setSubject(Long.toString(userPrincipal.getId()))
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public Long getUserIdFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();

        return Long.parseLong(claims.getSubject());
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException ex) {
            logger.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty");
        }
        return false;
    }
}

9. 最佳实践

9.1 安全配置最佳实践

@Configuration
@EnableWebSecurity
public class SecurityBestPractices {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 1. 禁用CSRF(仅用于API)
            .csrf(csrf -> csrf.disable())

            // 2. 配置CORS
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))

            // 3. 配置授权
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )

            // 4. 配置异常处理
            .exceptionHandling(exceptions -> exceptions
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                .accessDeniedHandler(new HttpStatusEntryPoint(HttpStatus.FORBIDDEN))
            )

            // 5. 配置会话管理
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            // 6. 添加安全头
            .headers(headers -> headers
                .frameOptions().deny()
                .contentTypeOptions().and()
                .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                    .maxAgeInSeconds(31536000)
                    .includeSubdomains(true)
                )
            );

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

9.2 密码安全最佳实践

@Component
public class PasswordSecurityService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    public boolean isPasswordStrong(String password) {
        // 至少8个字符
        if (password.length() < 8) {
            return false;
        }

        // 包含大小写字母、数字和特殊字符
        boolean hasUpper = password.matches(".*[A-Z].*");
        boolean hasLower = password.matches(".*[a-z].*");
        boolean hasNumber = password.matches(".*\\d.*");
        boolean hasSpecial = password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");

        return hasUpper && hasLower && hasNumber && hasSpecial;
    }

    public String generateSecurePassword() {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?";
        StringBuilder password = new StringBuilder();
        Random random = new SecureRandom();

        // 确保包含每种字符类型
        password.append(chars.charAt(random.nextInt(26))); // 大写字母
        password.append(chars.charAt(26 + random.nextInt(26))); // 小写字母
        password.append(chars.charAt(52 + random.nextInt(10))); // 数字
        password.append(chars.charAt(62 + random.nextInt(30))); // 特殊字符

        // 填充剩余字符
        for (int i = 4; i < 12; i++) {
            password.append(chars.charAt(random.nextInt(chars.length())));
        }

        // 打乱字符顺序
        char[] passwordArray = password.toString().toCharArray();
        for (int i = passwordArray.length - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            char temp = passwordArray[i];
            passwordArray[i] = passwordArray[j];
            passwordArray[j] = temp;
        }

        return new String(passwordArray);
    }
}

9.3 审计日志

@Entity
@Table(name = "audit_logs")
public class AuditLog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String action;

    @Column(nullable = false)
    private String resource;

    @Column
    private String details;

    @Column(nullable = false)
    private String ipAddress;

    @Column(nullable = false)
    private LocalDateTime timestamp;

    @Column(nullable = false)
    private String userAgent;

    // getters and setters
}

@Component
public class AuditService {

    @Autowired
    private AuditLogRepository auditLogRepository;

    public void logEvent(String action, String resource, String details) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication != null ? authentication.getName() : "ANONYMOUS";

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        AuditLog auditLog = new AuditLog();
        auditLog.setUsername(username);
        auditLog.setAction(action);
        auditLog.setResource(resource);
        auditLog.setDetails(details);
        auditLog.setIpAddress(getClientIpAddress(request));
        auditLog.setTimestamp(LocalDateTime.now());
        auditLog.setUserAgent(request.getHeader("User-Agent"));

        auditLogRepository.save(auditLog);
    }

    private String getClientIpAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
            return xForwardedFor.split(",")[0];
        }
        return request.getRemoteAddr();
    }
}

10. 常见问题

10.1 常见错误及解决方案

错误原因解决方案
Access Denied用户权限不足检查用户角色和权限配置
Invalid Credentials用户名或密码错误验证用户凭据
Session Expired会话超时重新登录或延长会话时间
CSRF Token MissingCSRF 保护启用但缺少 token在表单中添加 CSRF token
Maximum Sessions Exceeded超过最大会话数配置会话管理策略

10.2 性能优化建议

@Configuration
public class SecurityPerformanceConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 1. 使用无状态会话
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            // 2. 缓存用户信息
            .userDetailsService(cachedUserDetailsService())

            // 3. 优化密码编码器
            .passwordEncoder(passwordEncoder());

        return http.build();
    }

    @Bean
    public UserDetailsService cachedUserDetailsService() {
        return new CachingUserDetailsService(userDetailsService());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用合适的强度,避免过度加密
        return new BCryptPasswordEncoder(10);
    }
}

10.3 安全测试

@SpringBootTest
@AutoConfigureTestDatabase
class SecurityIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testPublicEndpointAccess() {
        ResponseEntity<String> response = restTemplate.getForEntity("/api/public/hello", String.class);
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }

    @Test
    void testProtectedEndpointWithoutAuth() {
        ResponseEntity<String> response = restTemplate.getForEntity("/api/user/profile", String.class);
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }

    @Test
    void testAdminEndpointWithUserRole() {
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(createUserToken());

        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(
            "/api/admin/users",
            HttpMethod.GET,
            entity,
            String.class
        );

        assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
    }

    private String createUserToken() {
        // 创建用户JWT token的逻辑
        return "user-jwt-token";
    }
}

总结

Spring Security 是一个功能强大且灵活的安全框架,通过本文档的学习,您应该能够:

  1. 理解核心概念:认证、授权、会话管理等
  2. 掌握架构原理:过滤器链、认证流程等
  3. 实现安全功能:表单登录、JWT 认证、OAuth2 等
  4. 应用最佳实践:密码安全、审计日志、性能优化等
  5. 解决常见问题:错误处理、调试技巧等

Spring Security 为 Spring 应用程序提供了全面的安全解决方案,通过合理配置和使用,可以有效保护应用程序免受各种安全威胁。

最近更新:: 2025/8/14 09:20
Contributors: Duke
Prev
Springboot
Next
SpringBoot 中的事件详解