本文适合刚接触Spring Security的开发者,以及准备Java面试的同学。从核心概念到实战配置,配合完整代码示例,让你快速上手企业级安全框架。
一、什么是Spring Security?
一句话:Spring Security是Spring家族的安全框架,负责认证(你是谁)和授权(你能干什么)。
用户请求 --> Spring Security过滤器链 --> 你的Controller ↓ ┌─────────────────┐ │ 1. 认证(你是谁)│ --> 用户名密码验证、Token验证 │ 2. 授权(能干嘛)│ --> 你有没有权限访问这个接口 └─────────────────┘
核心功能:
用户登录认证(用户名密码、OAuth2、JWT)
接口权限控制(RBAC角色权限)
防护攻击(CSRF、XSS、会话固定)
集成OAuth2/单点登录
二、核心概念速记
2.1 两个核心对象
// 1. Authentication(认证对象)- 代表"当前是谁" public interface Authentication extends Principal { Object getPrincipal(); // 用户身份(UserDetails对象) Object getCredentials(); // 密码/凭证 Collection<? extends GrantedAuthority> getAuthorities(); // 权限列表 } // 2. SecurityContext(安全上下文)- 存储当前认证信息 public interface SecurityContext { Authentication getAuthentication(); // 获取当前登录用户 }2.2 过滤器链(面试必问)
请求进入 ↓ SecurityContextPersistenceFilter // 加载SecurityContext ↓ UsernamePasswordAuthenticationFilter // 处理表单登录 ↓ BasicAuthenticationFilter // 处理Basic认证 ↓ ExceptionTranslationFilter // 处理认证/授权异常 ↓ FilterSecurityInterceptor // 权限校验(最终拦截) ↓ 进入Controller
记忆口诀:"上下文 → 认证 → 异常 → 授权"
三、快速入门(5分钟搞定)
3.1 引入依赖
<!-- 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.2 最简配置
/** * Spring Security配置类 * 引入依赖后自动生效,默认所有接口都需要登录 */ @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() // 公开接口 .anyRequest().authenticated() // 其他需要登录 ) .formLogin(form -> form .loginPage("/login") // 自定义登录页 .defaultSuccessUrl("/home") // 登录成功跳转 .permitAll() ) .logout(logout -> logout .logoutSuccessUrl("/login?logout") // 退出后跳转 ); return http.build(); } }3.3 测试接口
@RestController public class HelloController { @GetMapping("/public/hello") public String publicHello() { return "公开接口,无需登录"; } @GetMapping("/user/hello") public String userHello() { return "用户接口,需要登录"; } @GetMapping("/admin/hello") public String adminHello() { return "管理员接口,需要ADMIN角色"; } }启动效果:
访问
/public/hello→ 直接返回访问
/user/hello→ 跳转登录页默认用户名:
user,密码:控制台自动生成
四、用户认证实战
4.1 基于数据库的用户认证
第一步:用户实体
/** * 用户实体,实现UserDetails接口 */ @Entity @Table(name = "sys_user") @Data public class SysUser implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private Boolean enabled; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "sys_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private List<SysRole> roles; // 用户角色列表 // ========== UserDetails接口方法 ========== @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 返回用户的所有权限(角色也是一种权限) return roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) .collect(Collectors.toList()); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } } /** * 角色实体 */ @Entity @Table(name = "sys_role") @Data public class SysRole { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 角色名:ADMIN、USER等 private String description; }第二步:UserDetailsService实现
/** * 自定义用户加载服务 * 从数据库查询用户信息 */ @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户 SysUser user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException( "用户不存在:" + username )); return user; // SysUser已实现UserDetails } }第三步:配置认证管理器
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private CustomUserDetailsService userDetailsService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色 .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/home") .permitAll() ) .logout(logout -> logout .logoutSuccessUrl("/login?logout") ) .csrf(csrf -> csrf.disable()); // 前后端分离项目禁用CSRF return http.build(); } /** * 密码编码器(必须配置) * 推荐使用BCrypt强加密 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }第四步:测试用户注册
@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; /** * 用户注册 */ public SysUser register(String username, String password) { SysUser user = new SysUser(); user.setUsername(username); user.setPassword(passwordEncoder.encode(password)); // 密码加密 user.setEnabled(true); // 默认角色 SysRole defaultRole = new SysRole(); defaultRole.setName("USER"); user.setRoles(List.of(defaultRole)); return userRepository.save(user); } }五、JWT认证实战(前后端分离)
5.1 什么是JWT?
JWT结构:Header.Payload.Signature ┌─────────────────────────────────────────────────────────┐ │ eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.xxx │ │ ───────────────────── ─────────────────── ─── │ │ Header Payload Signature │ │ (算法) (用户信息) (签名) │ └─────────────────────────────────────────────────────────┘
5.2 引入JWT依赖
<!-- pom.xml --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>5.3 JWT工具类
/** * JWT工具类:生成和解析Token */ @Component public class JwtUtil { @Value("${jwt.secret}") private String secret; // 密钥,配置在application.yml @Value("${jwt.expiration}") private Long expiration; // 过期时间(毫秒) /** * 生成JWT Token */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put("roles", userDetails.getAuthorities()); return Jwts.builder() .claims(claims) .subject(userDetails.getUsername()) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + expiration)) .signWith(getSigningKey()) .compact(); } /** * 从Token中提取用户名 */ public String getUsernameFromToken(String token) { return getClaims(token).getSubject(); } /** * 验证Token是否有效 */ public boolean validateToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private Claims getClaims(String token) { return Jwts.parser() .verifyWith(getSigningKey()) .build() .parseSignedClaims(token) .getPayload(); } private boolean isTokenExpired(String token) { return getClaims(token).getExpiration().before(new Date()); } private SecretKey getSigningKey() { return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); } }5.4 JWT认证过滤器
/** * JWT认证过滤器 * 每次请求从Header中提取Token,验证后设置认证信息 */ @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private CustomUserDetailsService userDetailsService; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 1. 从Header获取Token String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); try { // 2. 提取用户名 String username = jwtUtil.getUsernameFromToken(token); // 3. 验证Token并设置认证信息 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 设置认证信息到SecurityContext SecurityContextHolder.getContext().setAuthentication(authToken); } } } catch (Exception e) { logger.error("JWT认证失败", e); } } filterChain.doFilter(request, response); } }5.5 认证接口
@RestController @RequestMapping("/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUtil jwtUtil; @Autowired private UserService userService; /** * 登录接口:验证用户名密码,返回JWT Token */ @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest request) { try { // 1. 验证用户名密码 Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( request.getUsername(), request.getPassword() ) ); // 2. 生成Token UserDetails userDetails = (UserDetails) authentication.getPrincipal(); String token = jwtUtil.generateToken(userDetails); // 3. 返回Token return ResponseEntity.ok(new JwtResponse(token)); } catch (BadCredentialsException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body("用户名或密码错误"); } } /** * 注册接口 */ @PostMapping("/register") public ResponseEntity<?> register(@RequestBody RegisterRequest request) { userService.register(request.getUsername(), request.getPassword()); return ResponseEntity.ok("注册成功"); } } // DTO类 @Data class LoginRequest { private String username; private String password; } @Data class RegisterRequest { private String username; private String password; } @Data @AllArgsConstructor class JwtResponse { private String token; }5.6 完整Security配置(JWT版)
@Configuration @EnableWebSecurity @EnableMethodSecurity // 启用方法级别权限控制 public class SecurityConfig { @Autowired private JwtAuthenticationFilter jwtAuthFilter; @Autowired private CustomUserDetailsService userDetailsService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // 前后端分离禁用CSRF .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态 .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/**").permitAll() // 认证接口公开 .requestMatchers("/public/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器 return http.build(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }5.7 application.yml配置
# JWT配置 jwt: secret: your-256-bit-secret-key-here-make-it-long-enough expiration: 86400000 # 24小时(毫秒) # 数据库配置 spring: datasource: url: jdbc:mysql://localhost:3306/security_demo username: root password: 123456 jpa: hibernate: ddl-auto: update show-sql: true六、权限控制实战
6.1 注解方式(推荐)
@RestController @RequestMapping("/api") public class ResourceController { /** * 任何已认证用户都可访问 */ @GetMapping("/user/info") @PreAuthorize("isAuthenticated()") public String getUserInfo() { return "用户信息"; } /** * 只有ADMIN角色可访问 */ @GetMapping("/admin/users") @PreAuthorize("hasRole('ADMIN')") public String listUsers() { return "用户列表"; } /** * USER或ADMIN角色都可访问 */ @GetMapping("/user/profile") @PreAuthorize("hasAnyRole('USER', 'ADMIN')") public String getProfile() { return "个人资料"; } /** * 需要特定权限(更细粒度) */ @DeleteMapping("/admin/user/{id}") @PreAuthorize("hasAuthority('DELETE_USER')") public String deleteUser(@PathVariable Long id) { return "删除用户:" + id; } /** * 方法执行后检查(返回值过滤) */ @GetMapping("/user/orders") @PostAuthorize("returnObject.owner == authentication.name") public Order getOrder() { return new Order("order1", "user1"); } }6.2 角色 vs 权限(Authority)
// 角色:ROLE_ADMIN、ROLE_USER(带ROLE_前缀) // 权限:DELETE_USER、READ_ORDER(自定义权限) // 配置示例 .requestMatchers("/admin/**").hasRole("ADMIN") // 检查角色 .requestMatchers("/delete/**").hasAuthority("DELETE_USER") // 检查权限 // 注解示例 @PreAuthorize("hasRole('ADMIN')") // 检查角色 @PreAuthorize("hasAuthority('DELETE_USER')") // 检查权限6.3 获取当前登录用户
@RestController public class UserController { /** * 方式1:通过SecurityContextHolder获取 */ @GetMapping("/me1") public String getCurrentUser1() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); return "当前用户:" + auth.getName(); } /** * 方式2:通过@AuthenticationPrincipal注解(推荐) */ @GetMapping("/me2") public String getCurrentUser2(@AuthenticationPrincipal UserDetails userDetails) { return "当前用户:" + userDetails.getUsername(); } /** * 方式3:注入Principal对象 */ @GetMapping("/me3") public String getCurrentUser3(Principal principal) { return "当前用户:" + principal.getName(); } }七、常见问题排查
问题1:403 Forbidden
// 原因1:CSRF未禁用(前后端分离必须禁用) .csrf(csrf -> csrf.disable()) // 原因2:角色前缀问题 // 错误:hasAuthority("ADMIN") // 正确:hasRole("ADMIN") // 自动加ROLE_前缀 // 原因3:权限不足 // 检查用户是否有对应角色/权限问题2:密码验证失败
// 原因:密码未加密或加密方式不匹配 // 正确做法:注册时加密 passwordEncoder.encode(rawPassword) // 验证时自动匹配 authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) )
问题3:CORS跨域问题
@Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOriginPatterns(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } } // Security配置中启用Cors http.cors(cors -> cors.configurationSource(corsConfigurationSource()));八、面试高频问题
Q1:Spring Security的核心组件有哪些?
答:
SecurityContextHolder:存储当前认证信息
Authentication:认证对象,包含用户信息和权限
UserDetailsService:加载用户信息的接口
PasswordEncoder:密码加密器
SecurityFilterChain:过滤器链配置
Q2:认证流程是怎样的?
答:
1. 用户提交用户名密码 2. UsernamePasswordAuthenticationFilter拦截请求 3. 封装成Authentication对象 4. AuthenticationManager调用UserDetailsService加载用户 5. PasswordEncoder验证密码 6. 认证成功,将Authentication存入SecurityContext
Q3:JWT和Session的区别?
答:
对比 Session JWT 存储位置 服务端 客户端 扩展性 需要共享Session 天然支持分布式 安全性 依赖Cookie 可自定义存储 有效期 会话结束 可自定义 适用场景 单体应用 前后端分离/微服务
Q4:如何实现"记住我"功能?
答:
http.rememberMe(remember -> remember .tokenRepository(persistentTokenRepository()) // 持久化Token .tokenValiditySeconds(86400 * 7) // 7天有效 .userDetailsService(userDetailsService) );
Q5:@PreAuthorize和@Secured的区别?
答:
@PreAuthorize:支持SpEL表达式,功能更强(推荐)
@Secured:简单角色匹配,不支持SpEL@PreAuthorize("hasRole('ADMIN') and #id > 0") // 支持复杂表达式 @Secured("ROLE_ADMIN") // 仅支持简单角色
九、最佳实践总结
密码必须加密:使用BCrypt,不要用MD5
前后端分离用JWT:无状态,易扩展
禁用CSRF:前后端分离项目必须禁用
细粒度权限控制:用
@PreAuthorize注解敏感操作二次验证:修改密码、支付等
日志记录:记录登录失败、权限拒绝等事件
十、项目结构参考
src/main/java/com/example/security/ ├── config/ │ └── SecurityConfig.java # Security配置 ├── controller/ │ ├── AuthController.java # 认证接口 │ └── UserController.java # 业务接口 ├── entity/ │ ├── SysUser.java # 用户实体 │ └── SysRole.java # 角色实体 ├── repository/ │ └── UserRepository.java # 用户DAO ├── service/ │ ├── CustomUserDetailsService.java # 用户加载服务 │ └── UserService.java # 业务服务 ├── util/ │ └── JwtUtil.java # JWT工具类 └── filter/ └── JwtAuthenticationFilter.java # JWT过滤器
十一、推荐阅读
Spring Security官方文档
[Spring Security实战](陈木鑫著)
JWT官网
作者简介:Java后端开发,专注Spring生态与微服务架构。
如果本文对你有帮助,欢迎点赞收藏!有问题欢迎评论区讨论。