Spring Security从入门到实战:一文搞懂认证与授权

📅 2026/6/26 8:03:31
Spring Security从入门到实战:一文搞懂认证与授权
本文适合刚接触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 groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency3.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 ListSysRole 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 什么是JWTJWT结构Header.Payload.Signature ​ ┌─────────────────────────────────────────────────────────┐ │ eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.xxx │ │ ───────────────────── ─────────────────── ─── │ │ Header Payload Signature │ │ (算法) (用户信息) (签名) │ └─────────────────────────────────────────────────────────┘5.2 引入JWT依赖!-- pom.xml -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.12.6/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.12.6/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.12.6/version scoperuntime/scope /dependency5.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) { MapString, 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(); } }七、常见问题排查问题1403 Forbidden// 原因1CSRF未禁用前后端分离必须禁用 .csrf(csrf - csrf.disable()) ​ // 原因2角色前缀问题 // 错误hasAuthority(ADMIN) // 正确hasRole(ADMIN) // 自动加ROLE_前缀 ​ // 原因3权限不足 // 检查用户是否有对应角色/权限问题2密码验证失败// 原因密码未加密或加密方式不匹配 ​ // 正确做法注册时加密 passwordEncoder.encode(rawPassword) ​ // 验证时自动匹配 authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) )问题3CORS跨域问题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()));八、面试高频问题Q1Spring Security的核心组件有哪些答SecurityContextHolder存储当前认证信息Authentication认证对象包含用户信息和权限UserDetailsService加载用户信息的接口PasswordEncoder密码加密器SecurityFilterChain过滤器链配置Q2认证流程是怎样的答1. 用户提交用户名密码 2. UsernamePasswordAuthenticationFilter拦截请求 3. 封装成Authentication对象 4. AuthenticationManager调用UserDetailsService加载用户 5. PasswordEncoder验证密码 6. 认证成功将Authentication存入SecurityContextQ3JWT和Session的区别答对比SessionJWT存储位置服务端客户端扩展性需要共享Session天然支持分布式安全性依赖Cookie可自定义存储有效期会话结束可自定义适用场景单体应用前后端分离/微服务Q4如何实现记住我功能答http.rememberMe(remember - remember .tokenRepository(persistentTokenRepository()) // 持久化Token .tokenValiditySeconds(86400 * 7) // 7天有效 .userDetailsService(userDetailsService) );Q5PreAuthorize和Secured的区别答PreAuthorize支持SpEL表达式功能更强推荐Secured简单角色匹配不支持SpELPreAuthorize(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生态与微服务架构。如果本文对你有帮助欢迎点赞收藏有问题欢迎评论区讨论。