适用人群:Java 后端开发、Spring Boot 初学者、对系统安全一知半解的小白
技术栈:Java 17 + Spring Boot 3.2 + Spring Security + JWT
目标:从零配置登录认证,到实现权限控制,再到生产级安全加固,一步到位!
🔒 一、为什么需要 Spring Security?
想象一下:你的网站有用户注册、后台管理、支付接口……
如果没有安全防护,会发生什么?
- 任何人都能访问
/admin/deleteAllUsers - 用户 A 可以冒充用户 B 修改订单
- 密码明文存储,数据库一泄露,全站崩盘
Spring Security 就是你的“数字保安”,它帮你:
- 验证用户身份(Authentication)
- 控制资源访问权限(Authorization)
- 防御 CSRF、XSS、会话固定等常见攻击
❌ 二、反例:没有安全的系统有多危险?
// ❌ 危险!任何人都能删除用户! @RestController public class UserController { @DeleteMapping("/user/{id}") public String deleteUser(@PathVariable Long id) { userService.delete(id); // 无任何权限校验! return "删除成功"; } }后果:黑客只需发送一个 DELETE 请求,你的用户数据就没了!
✅ 三、入门:5 分钟实现基础登录认证
步骤 1:创建 Spring Boot 项目
使用 start.spring.io,选择:
- Spring Web
- Spring Security
- Spring Data JPA(可选,用于用户存储)
步骤 2:添加依赖(pom.xml)
<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>步骤 3:启动应用,观察默认行为
- 访问
http://localhost:8080/any-path - 自动跳转到
/login页面 - 控制台打印一行密码(如
Using generated security password: a1b2c3d4...)
✅ 这就是 Spring Security 的默认安全策略:所有请求必须认证!
🛠️ 四、自定义用户认证(基于内存)
场景:我们想用自己定义的用户名/密码登录
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public UserDetailsService userDetailsService() { UserDetails user = User.builder() .username("admin") .password("{noop}123456") // {noop} 表示不加密(仅演示!) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() // 公开接口 .anyRequest().authenticated() // 其他需登录 ) .formLogin(form -> form .loginPage("/login") // 自定义登录页 .permitAll() ) .logout(logout -> logout .permitAll() ); return http.build(); } }⚠️ 注意:
{noop}表示明文密码,绝对不能用于生产环境!
🔐 五、生产级密码加密(必须做!)
反例:明文存密码
// ❌ 千万别这么干! passwordEncoder.encode("123456"); // 如果 encoder 是 NoOpPasswordEncoder正确做法:使用 BCrypt 加密
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 注册用户时加密 String encodedPassword = passwordEncoder.encode("123456"); UserDetails user = User.builder() .username("admin") .password(encodedPassword) // 存的是加密后的字符串 .roles("ADMIN") .build();✅ BCrypt 是单向哈希,即使数据库泄露,也无法反推原始密码!
🧩 六、基于角色的权限控制(RBAC)
场景:普通用户只能看自己的订单,管理员能删所有订单
@RestController public class OrderController { // 所有登录用户可访问 @GetMapping("/orders") public List<Order> getOrders(Authentication auth) { String username = auth.getName(); return orderService.findByUser(username); } // 仅 ADMIN 角色可访问 @DeleteMapping("/orders/{id}") @PreAuthorize("hasRole('ADMIN')") // 关键注解! public String deleteOrder(@PathVariable Long id) { orderService.delete(id); return "删除成功"; } }💡 要启用
@PreAuthorize,需在配置类加:
@EnableMethodSecurity // 替代旧版 @EnableGlobalMethodSecurity🪪 七、JWT 无状态认证(适合前后端分离)
为什么用 JWT?
- 传统 Session 有状态,不适合分布式
- JWT 无状态,前端每次携带 Token 即可
实现步骤:
1. 添加 JWT 工具类
@Component public class JwtUtil { private String secret = "MySecretKey123!@#"; // 生产环境用更长的密钥 public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24小时 .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String extractUsername(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token) .getBody().getSubject(); } public boolean validateToken(String token, String username) { String extractedUsername = extractUsername(token); return (extractedUsername.equals(username) && !isTokenExpired(token)); } }2. 自定义 Filter 拦截 Token
@Component public class JwtAuthFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String header = request.getHeader("Authorization"); String username = null; String jwt = null; if (header != null && header.startsWith("Bearer ")) { jwt = header.substring(7); try { username = jwtUtil.extractUsername(jwt); } catch (Exception e) { // token 无效 } } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); } } chain.doFilter(request, response); } }3. 配置 Security 使用 JWT
@Bean public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthFilter jwtFilter) throws Exception { http .csrf(csrf -> csrf.disable()) // JWT 通常禁用 CSRF .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/login").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }4. 登录接口返回 Token
@PostMapping("/auth/login") public ResponseEntity<?> login(@RequestBody LoginRequest req) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(req.getUsername(), req.getPassword()) ); UserDetails user = userDetailsService.loadUserByUsername(req.getUsername()); String token = jwtUtil.generateToken(user.getUsername()); return ResponseEntity.ok(Map.of("token", token)); }⚠️ 八、安全注意事项(血泪教训!)
| 风险 | 正确做法 |
|---|---|
| 密码明文存储 | 必须用BCryptPasswordEncoder |
| CSRF 攻击 | 表单提交场景开启 CSRF;JWT 无状态可关闭 |
| 敏感信息泄露 | 错误信息不要暴露内部细节(如 SQL) |
| 暴力破解 | 登录失败锁定账户或验证码 |
| Token 泄露 | 设置合理过期时间,支持 Token 黑名单 |
| 权限绕过 | 前端隐藏按钮 ≠ 后端无校验!后端必须二次验证 |
📌 九、总结:Spring Security 核心思想
- 认证(Authentication):你是谁? → 登录、Token 验证
- 授权(Authorization):你能做什么? → 角色、权限控制
- 防御(Defense):防攻击 → CSRF、XSS、会话管理
Spring Security 不是“加个依赖就安全”,而是一套完整的安全体系。理解其原理,才能灵活应对各种场景。
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!(发点评论可以给博主加热度哦)