Spring Security基础:快速搭建安全认证系统

杨智★
2025-12-15 18:49
阅读 612

凌晨1点27分,窗外的县城已经彻底安静下来,只有偶尔传来几声狗叫。我泡了杯速溶咖啡(别笑,小镇做题家哪有手冲的条件),盯着屏幕上的Spring Boot项目,心想:这玩意儿要是能像Go写HTTP服务那样简单就好了。

在这家公司待了三年多,从一个只会System.out.println的菜鸟,到现在能被叫一声“后端主力”,说实话,成长了不少。但最近总觉得有点卡住——技术栈太稳了,稳到我都快睡着了。产品需求永远是“加个按钮”、“改个字段”,连运维都懒得搭理我们这种小县城远程团队,反正“你们自己搞定就行”。

上周五晚上,产品经理突然在企业微信上@我:“下个月要上线新模块,得加登录和权限控制,最好下周给个Demo。” 我盯着消息看了三秒,心里默默翻了个白眼:你当我是AI吗?不过转念一想,也好,正好趁这个机会把拖了半年的Spring Security捡起来。毕竟,跳槽简历上总不能只写“熟练使用MyBatis”吧?


为啥不用Go?或者…区块链?

先说清楚,这篇文章讲的是Spring Security,不是Go,也不是区块链。但既然要求提到,那我就坦白交代一下我的内心戏。

前阵子确实折腾过用Go写认证服务——Gin + JWT,跑得飞快,内存占用不到Java的三分之一。但问题来了:公司老系统全是Spring全家桶,MySQL + Redis + RabbitMQ那一套,突然插个Go服务进去,运维大哥直接发语音吼我:“你让K8s怎么管?日志怎么统一?监控告警谁对接?” 得,打住。

至于区块链……咳,去年双11期间,领导不知从哪听来的风,说“用户数据要上链才安全”。我当时差点把键盘砸了——我们做的就是一个后台管理系统,用户量还没小区业主群人多,上什么链?最后不了了之。不过这事让我意识到:安全不是堆概念,而是解决真实问题。比如——别让实习生删了生产库。

所以,回归现实:用Spring Security,稳,熟,团队都能维护。


需求场景:一个再普通不过的后台系统

我们要做的,其实就三件事:

  1. 用户登录(用户名+密码)
  2. 登录后访问受保护接口
  3. 不同角色看到不同菜单(RBAC)

听起来简单?但上次我手写过滤器+Token验证,结果因为没处理CSRF,被测试小妹提了个高危漏洞。那次线上事故后,我发誓:认证授权这种事,别自己造轮子

Spring Security虽然配置有点“魔法”,但人家是经过千锤百炼的。而且,它和Spring Boot整合得贼丝滑,自动配置能省掉80%的样板代码。


实战:从零搭建,不踩坑是不可能的

第一步:依赖安排上

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</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>

注意:别忘了加spring-boot-starter-web!我第一次就漏了,启动直接报错,还以为Security有问题。

第二步:最简配置(内存用户)

为了快速验证,先搞个内存用户:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("admin")
            .password("{bcrypt}$2a$10$...") // 这里用BCrypt加密后的密码
            .roles("ADMIN")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

这时候访问 /,会自动跳转到 /login 页面——Spring Security自带的登录页虽然丑,但能用!

血泪教训:密码一定要用 {bcrypt} 前缀!否则默认用DelegatingPasswordEncoder会报There is no PasswordEncoder mapped for the id "null"。我第一次直接写明文密码,调试半小时才发现是加密格式问题。

第三步:接入数据库(这才是生产该有的样子)

内存用户只能玩玩,真上生产必须用数据库。表结构建议如下:

字段 类型 说明
id BIGINT 主键
username VARCHAR(50) 唯一
password VARCHAR(100) BCrypt加密后存储
enabled TINYINT 是否启用(防逻辑删除)

对应的User实体:

@Entity
@Table(name = "sys_user")
public class SysUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private Boolean enabled = true;
}

然后自定义UserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword()) // 数据库里已经是BCrypt加密过的
            .roles("USER") // 这里可以查role表动态赋值
            .build();
    }
}

注意:不要在数据库存明文密码!注册或修改密码时,记得用BCryptPasswordEncoder加密:

@Autowired
private PasswordEncoder passwordEncoder;

// 注册时
user.setPassword(passwordEncoder.encode(rawPassword));

接口设计 & Token方案

虽然Spring Security默认用Session,但前后端分离项目一般用JWT。这里我选择保留Session——原因很简单:我们前端是Vue+ElementUI,部署在同一域名下,Cookie自动携带,省心。

但如果你们是纯API服务(比如给App提供接口),那就要上JWT了。思路是:

  1. 登录接口返回Token
  2. 后续请求Header带 Authorization: Bearer <token>
  3. 自定义OncePerRequestFilter解析Token并设置SecurityContext

不过这次项目没这个需求,我就没折腾。稳定压倒一切,这是我在这小县城悟出的道理。


权限控制:方法级注解真香

光有登录不够,还得控制谁能干啥。Spring Security的方法级安全简直神器:

@RestController
public class OrderController {

    @PreAuthorize("hasRole('ADMIN')")
    @DeleteMapping("/orders/{id}")
    public Result deleteOrder(@PathVariable Long id) {
        // 只有ADMIN能删订单
        orderService.delete(id);
        return Result.success();
    }

    @PreAuthorize("authentication.name == #userId")
    @GetMapping("/users/{userId}/profile")
    public UserProfile getProfile(@PathVariable String userId) {
        // 只能看自己的资料
        return userService.getProfile(userId);
    }
}

开启方法安全只需一行注解:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
}

吐槽时间:测试小妹第一次看到@PreAuthorize("authentication.name == #userId")时,惊呼“这还能这么写?!” 其实Spring EL表达式支持很多操作,甚至能调用自定义Bean方法,灵活到飞起。


性能 & 运维经验

虽然Spring Security很重,但在我们这种日活几百的小系统里,完全无感。不过还是有几个生产经验分享:

  1. 缓存UserDetails:如果用户信息不常变,可以用@Cacheable缓存loadUserByUsername的结果,减少DB压力。
  2. 日志打全:登录失败、权限拒绝等事件,一定要记录日志,方便审计。可以用AuthenticationFailureHandlerAccessDeniedHandler定制。
  3. CSRF别关:除非你是纯API服务,否则千万别关CSRF防护!我们之前关了,结果被扫出XSS+CSRF组合拳漏洞。
  4. 密码策略:至少8位,包含大小写+数字。别让用户设123456

最后:小镇做题家的思考

折腾完这套认证系统,花了两个晚上。虽然不算复杂,但比手写过滤器靠谱多了。更重要的是,我不再怕“安全”这个词了

有人说,县城程序员接触不到高并发、大数据,技术会落后。但我觉得,能把基础打牢,把安全、事务、异常处理这些细节做好,比盲目追新更重要。Go再快,也得有人维护;区块链再火,也得解决实际问题。

下周我要去面试一家远程公司,JD里写着“熟悉Spring Security优先”。呵,这下稳了。

对了,如果你也在小城市coding,欢迎留言交流。说不定哪天,我们能在GitHub上互相点个star呢。


附:常用配置速查表

配置项 说明 示例
permitAll() 允许匿名访问 .requestMatchers("/api/public/**").permitAll()
authenticated() 需要认证 .anyRequest().authenticated()
hasRole('ADMIN') 需要ADMIN角色 @PreAuthorize("hasRole('ADMIN')")
hasAuthority('order:delete') 需要具体权限 更细粒度控制
rememberMe() 记住我功能 .rememberMe().tokenValiditySeconds(86400)

好了,咖啡凉了,该睡觉了。明天还要改产品提的“紧急需求”——他说要把登录页背景换成渐变色。

评论 0

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