Spring Security实战:从零搭建一个安全认证系统

Promise追梦人
2025-06-29 20:39
阅读 366

去年我所在的公司启动了一个企业级SaaS项目,需要为后台管理系统和开放平台分别构建用户身份认证体系。作为一个后端开发者,我第一时间想到的就是用Spring Security来实现这一块功能——毕竟这是Java生态中最成熟、最广泛使用的安全框架了。

但在实际开发过程中,我们遇到了不少问题:比如如何统一管理不同用户类型(如普通员工、客户经理、第三方开发者)的权限;如何与现有的RBAC模型整合;还有性能瓶颈、并发冲突以及在分布式环境下保持登录状态等问题。这让我意识到,虽然Spring Security功能强大,但如果只是照搬官方文档,不做深入理解,在真实项目里是会踩坑的。

这篇文章就结合我们在项目中使用Spring Security的实际经验,谈谈如何快速搭建一个既能满足业务需求又能稳定运行的安全认证系统。


背景说明:为什么选择Spring Security?

背景说明:为什么选择Spring Security?

我们的项目需要同时支持内部员工和外部客户的访问:

  • 内部员工:通过单点登录(SSO)进入后台管理系统;
  • 外部客户:通过API接入我们的开放平台,需支持OAuth2;
  • 第三方开发者:可以申请Access Token进行测试调用。

最初我们想自己写一套认证逻辑,但发现随着需求越来越复杂(如密码策略、多因素认证、角色继承等),维护成本越来越高。最终决定采用Spring Security,并在其基础上做扩展定制。

事实证明,这个决定非常正确。Spring Security不仅解决了我们80%的核心问题,还提供了灵活的扩展机制,让我们能根据业务特性定制自己的认证流程。


挑战:不是“引入依赖就能跑”的事

挑战:不是“引入依赖就能跑”的事

第一次在项目中集成Spring Security的时候,我按照教程添加了基本依赖和@EnableWebSecurity注解,然后配置了内存用户信息。看起来一切正常——可当我把这套代码推到测试环境的时候,问题就来了:

  1. 用户数据存储在MySQL中,而默认配置是内存中的用户
    这导致登录失败率高达90%,因为数据库里的用户压根没加载进去。

  2. 未考虑跨域请求场景
    前端用Vue开发的前端页面部署在不同的域名下,一上来就被CORS拦截得死死的,连登录接口都进不去。

  3. 没有处理CSRF保护机制
    即便允许了跨域,表单提交时又遇到403错误,原来是Spring Security默认开启了CSRF防护。

  4. 忽略了HTTPS的强制要求
    测试环境用了HTTP,结果某些浏览器直接阻止了Cookie的写入,导致无法维持会话状态。

这些问题让我深刻意识到:Spring Security看似开箱即用,但要用好它,必须理解它的默认行为以及背后的安全机制。


解决方案设计:灵活而可靠的架构

解决方案设计:灵活而可靠的架构

我们的目标很明确:构建一个既支持传统Session机制、又兼容OAuth2协议的统一认证中心(Auth Center),同时还能对接多个子系统的安全需求。

整体架构如下图所示:

+------------------+        +-----------------------+
|  Authentication  |<------>|       Auth Center      |
|     Service      |        | (Spring Security + JWT)|
+------------------+        +-----------------------+
           |                              |
           |                +----------------------------+
           |                |         Resource Server    |
           |                | (User Management System)   |
           +-------------->+----------------------------+
                            |
                            |                +----------------------------+
                            +--------------->+         API Gateway        |
                                             | (Open Platform, OAuth2)    |
                                             +----------------------------+

数据库设计模型-1

为了达到这个目标,我们采用了以下关键策略:

1. 使用Spring Data JPA + 自定义UserDetailsService

我们没有使用Spring Security内置的InMemoryUserDetailsManager,而是实现了自己的UserDetailsService,从MySQL中加载用户信息。

@Service
public class CustomUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));

        return User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(user.getAuthorities()) // 权限转换
                .build();
    }
}

这样我们就可以完全控制用户数据源,也能配合现有的RBAC权限模型。


2. Session共享:Redis + Spring Session

由于未来可能需要集群部署,我们采用了Redis来做Session持久化。这一步非常关键,否则负载均衡环境下会出现登录状态不一致的问题。

核心配置如下:

spring:
  session:
    store-type: redis
  redis:
    host: localhost
    port: 6379

并添加了相关依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

之后,只需要在配置类上加上@EnableRedisHttpSession即可启用。


3. 支持多种认证方式:JWT + OAuth2

对于前后端分离的应用(比如我们的前端管理系统),我们使用JWT做无状态认证:

// 生成Token示例
String token = Jwts.builder()
    .setSubject(user.getUsername())
    .claim("authorities", authorities)
    .setExpiration(new Date(System.currentTimeMillis() + 86400000))
    .signWith(SignatureAlgorithm.HS512, "secret_key")
    .compact();

而对于开放平台,则使用Spring Security提供的OAuth2支持,集成Google、GitHub等第三方认证服务。


4. CSRF & CORS 配置

这部分我们在初期忽视了,后来专门针对前端访问做了调整:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .cors().and()
        .authorizeRequests()
        ...
}

并且在配置文件中加入CORS规则:

spring:
  cors:
    allowed-origins: http://localhost:3000
    allowed-methods: GET,POST,PUT,DELETE

代码实践:从零开始搭建认证模块

下面是一个典型的Spring Boot + Spring Security整合样例结构:

1. 添加核心依赖

<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- 数据库相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

2. 核心配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .cors().and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated());
        
        return http.build();
    }

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

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

3. 登录接口实现示例

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

    @Autowired
    private AuthenticationManager authenticationManager;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginDto dto) {
        Authentication authenticate = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(dto.username(), dto.password()));
        
        String token = jwtService.generateToken(((UserDetails) authenticate.getPrincipal()).getUsername());

        return ResponseEntity.ok().header("Authorization", "Bearer " + token).build();
    }
}

开发过程中踩过的几个坑

1. 用户权限信息丢失

我们在实现自定义UserDetails时,忘记将用户的权限(authorities)放进User对象里,导致所有登录用户都无法访问任何受保护资源。

解决方法:确保UserDetails实现类返回了正确的权限集合。

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return authorities; // 必须是GrantedAuthority类型集合
}

2. 不同客户端之间Session混乱

一开始我们用了本地Session存储,结果微服务集群部署之后,同一个用户被分发到不同实例上,导致多次登录。后来才引入Redis作为Session存储,问题得以解决。


3. CSRF误禁用导致安全漏洞

早期为了方便测试,我们简单粗暴地csrf().disable(),后来被安全团队指出这是一个安全隐患,特别是在内部系统中有XSS攻击风险。因此我们重新启用了CSRF,并对前端传递的token进行了校验。


实施后的效果和收益

经过两个版本迭代,我们成功上线了认证模块,并且达到了以下几个关键指标:

  • 登录成功率提升至99%以上;
  • 多用户类型的权限控制清晰可控;
  • 在Kubernetes集群中实现了无感知登录;
  • 与其他服务的集成时间缩短了至少30%;
  • 安全审计报告通过率达100%。

更重要的是,有了统一的认证中心之后,其他业务系统接入非常容易,只需配置相应的Client ID和Secret即可完成OAuth2认证接入。


我的一些建议和注意事项

如果你正在考虑用Spring Security搭建安全认证系统,以下几点经验或许对你有帮助:

✅ 初期不要盲目追求自定义

很多同学喜欢一开始就重写整个过滤器链,但实际上,先熟悉Spring Security的默认行为远比强行自定义更重要。搞清楚默认的Filter执行顺序、Session生命周期、认证逻辑后再做扩展,省去很多弯路。

✅ 技术选型要早定,别临时换框架

我们一开始还在犹豫是否用Shiro替代Spring Security,结果中途切换浪费了不少时间。建议尽早确定技术栈,避免后期重构。

✅ 多人协作要统一配置规范

在团队开发中,不同的人可能会写出风格迥异的Security配置。建议统一使用配置类(而非注解),并在代码审查阶段特别注意安全性相关的部分。

✅ 性能方面也要考虑优化空间

虽然Spring Security自身性能还不错,但如果是高并发场景,比如每秒数万次的认证请求,还是要注意:

  • 不要在认证流程中频繁查询数据库;
  • 使用缓存降低重复鉴权压力;
  • 合理配置Session超时时间;
  • 避免过度开启不必要的安全验证项。

小插曲:一次深夜修复的教训

有一次上线前夜,我们突然发现某个租户的用户居然能越权访问另一个租户的数据。排查发现是因为我们在SecurityContext中只保存了用户名,没有记录租户ID。

这个问题暴露了我们之前设计的不足。后来我们改成了在认证阶段就把租户信息写入Principal中,并在整个业务层做租户隔离校验。

那一刻我才真正意识到:安全不是加几层防火墙的事,它体现在每一行代码的设计决策里。


结语:安全体系建设没有终点

Spring Security是个强大的工具,但它只是一个起点。真正保障系统安全的,是我们对业务的理解、对技术细节的把控,以及对潜在威胁的预判能力。

希望通过这篇分享,能让刚接触Spring Security的你少走一些弯路,也让已经熟练使用的你在实践中更加从容。

如果你也经历过类似的挑战或踩坑,欢迎留言交流!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝