Spring Security 安全框架详解:从入门到实战 🔐
Spring Security 是 Spring 生态中用于处理认证(Authentication)和授权(Authorization) 的安全框架,它强大、灵活、可扩展,几乎是所有 Java Web 应用的标配安全解决方案。本文将带你全面理解 Spring Security 的核心概念、工作原理以及实战应用!💪
📚 目录导航
一、Spring Security 概述:什么是安全框架? 1.1 为什么需要安全框架? 在 Web 应用中,我们经常会遇到以下安全问题:
flowchart TD
A["⚠️ Web 应用常见安全问题"] --> B["🔓 未授权访问"]
A --> C["🔐 密码泄露"]
A --> D["🚫 CSRF 攻击"]
A --> E["🐛 SQL 注入"]
A --> F["🕵️ XSS 攻击"]
B --> B1["访问未授权\n的功能或数据"]
C --> C1["用户密码\n被窃取"]
D --> D1["跨站请求\n伪造攻击"]
E --> E1["恶意 SQL\n注入攻击"]
F --> F1["恶意脚本\n注入攻击"]
style A fill:#fff3e0
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#fff3e0
style E fill:#ffcdd2
style F fill:#fff3e0
常见的 Web 安全威胁:
未授权访问 :用户没有登录或没有权限,却能访问某些功能
密码泄露 :用户密码在传输或存储过程中被窃取
CSRF 攻击 :攻击者诱导用户执行非本意的操作
SQL 注入 :通过输入框注入恶意 SQL 语句
XSS 攻击 :在页面中注入恶意脚本
1.2 Spring Security 是什么? Spring Security 是一个专注于为 Java 应用提供认证和授权 的安全框架。它的主要功能包括:
flowchart TD
A["🔐 Spring Security 核心功能"] --> B["🕵️ 身份认证"]
A --> C["🔑 授权管理"]
A --> D["🛡️ CSRF 防护"]
A --> E["🔒 会话管理"]
A --> F["📊 密码加密"]
A --> G["🌐 第三方登录"]
B --> B1["用户名密码认证\nOAuth2 认证\nJWT 认证\nLDAP 认证"]
C --> C1["基于角色权限\n基于资源权限\n方法级别权限"]
D --> D1["Token 防护\n表单防护"]
E --> E1["会话固定防护\n并发控制"]
style A fill:#fff3e0
style B fill:#c8e6c9
style C fill:#c8e6c9
style D fill:#e3f2fd
style E fill:#fff3e0
style F fill:#f8bbd0
style G fill:#e3f2fd
1.3 Spring Security 的优势
特性
说明
功能完善
提供认证、授权、会话管理等完整安全解决方案
易于集成
与 Spring Boot、Spring MVC 无缝集成
高度可扩展
支持自定义认证方式、各种第三方登录
社区活跃
作为 Spring 家族成员,拥有强大的社区支持
持续更新
跟随 Spring 生态持续迭代,安全性高
1.4 Spring Security 版本选择
版本
Spring Boot 版本
Java 版本要求
主要特性
Spring Security 5.x
Spring Boot 2.x
Java 8+
OAuth2 集成、JWT 支持
Spring Security 6.x
Spring Boot 3.x
Java 17+
响应式安全、简化 API
💡 推荐 :新项目建议使用 Spring Boot 3.x + Spring Security 6.x,享受最新特性和性能优化。
二、核心概念:认证与授权 2.1 认证(Authentication) 认证 是验证用户身份的过程,通俗来说就是”确认你是谁”。
常见的认证方式:
flowchart TD
A["🕵️ 认证方式"] --> B["🔐 用户名密码"]
A --> C["📱 手机验证码"]
A --> D["🔑 数字证书"]
A --> E["🌐 OAuth2"]
A --> F["👆 指纹/面部识别"]
style A fill:#fff3e0
style B fill:#c8e6c9
style C fill:#e3f2fd
style D fill:#fff3e0
style E fill:#f8bbd0
style F fill:#e3f2fd
2.2 授权(Authorization) 授权 是在认证之后,根据用户的身份决定”你能做什么”。
常见的授权模型:
模型
说明
示例
RBAC
基于角色的访问控制
用户 -> 角色 -> 权限
ABAC
基于属性的访问控制
根据用户属性动态判断
DAC
自主访问控制
资源所有者自行分配权限
2.3 认证与授权的关系
flowchart LR
A["👤 用户"] -->|"1️⃣ 认证"| B["🕵️ 身份验证"]
B -->|"验证成功"| C["🆔 身份标识"]
C -->|"2️⃣ 授权"| D["🔑 权限判断"]
D -->|"有权限"| E["✅ 访问资源"]
D -->|"无权限"| F["❌ 拒绝访问"]
style B fill:#c8e6c9
style D fill:#e3f2fd
典型流程:
用户登录系统 → 系统验证用户名密码 → 认证成功,发放 Token
用户访问某个功能 → 系统检查用户角色 → 判断是否有权限
有权限 → 允许访问;无权限 → 拒绝访问
三、快速入门:五分钟跑通 Spring Security 3.1 添加依赖 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > <version > 2.7.x</version > </dependency >
3.2 最简配置:开箱即用 Spring Boot 环境下,只需添加依赖,无需任何配置,框架会自动开启安全保护 :
1 2 3 4 5 6 7 8 9 @SpringBootApplication public class Application { public static void main (String[] args) { SpringApplication.run(Application.class, args); } }
添加依赖后,Spring Security 会自动配置:
🔒 所有端点需要认证
🔐 默认生成随机密码(可在控制台看到)
🔄 启用 CSRF 防护
🔐 默认启用会话管理
3.3 自定义安全配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/static/**" , "/public/**" ).permitAll() .requestMatchers("/login" , "/register" ).permitAll() .requestMatchers("/admin/**" ).hasRole("ADMIN" ) .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login" ) .defaultSuccessUrl("/home" ) .failureUrl("/login?error" ) .permitAll() ) .httpBasic(basic -> basic .realmName("My App" ) ) .logout(logout -> logout .logoutUrl("/logout" ) .logoutSuccessUrl("/login?logout" ) .invalidateHttpSession(true ) .deleteCookies("JSESSIONID" ) ) .sessionManagement(session -> session .sessionFixation().changeSessionId() .maximumSessions(1 ) .maxSessionsPreventsLogin(false ) ); return http.build(); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } }
3.4 自定义用户服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null ) { throw new UsernameNotFoundException ("用户不存在:" + username); } List<SimpleGrantedAuthority> authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority ("ROLE_" + role.getName())) .collect(Collectors.toList()); return new org .springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), user.getStatus() == 1 , true , true , true , authorities ); } }
四、Spring Security 架构与工作流程 4.1 核心组件架构
flowchart TD
A["🔐 Spring Security 架构"] --> B["🏠 SecurityContextHolder"]
A --> C["🎭 Authentication"]
A --> D["🔓 SecurityFilterChain"]
A --> E["🛡️ AccessDecisionManager"]
B --> B1["存储当前用户\n认证信息"]
C --> C1["Principal\nCredentials\nAuthorities"]
D --> D1["UsernamePassword\nAuthenticationFilter"]
D --> D2["...\n其他过滤器"]
E --> E1["投票器\n决策管理器"]
style A fill:#fff3e0
4.2 认证流程详解
flowchart TD
A["🔐 Spring Security 认证流程"] --> B["1️⃣ 用户提交\n用户名密码"]
B --> C["2️⃣ Filter 拦截\n请求"]
C --> D["3️⃣ AuthenticationManager\n管理认证"]
D --> E["4️⃣ AuthenticationProvider\n执行认证"]
E --> F["5️⃣ UserDetailsService\n加载用户信息"]
F --> G["6️⃣ PasswordEncoder\n校验密码"]
G --> H{"认证结果?"}
H -->|"成功"| I["7️⃣ SecurityContext\n保存认证信息"]
H -->|"失败"| J["8️⃣ 返回\n认证失败"]
I --> K["9️⃣ AuthenticationSuccessHandler\n处理成功"]
J --> K2["🔙 AuthenticationFailureHandler\n处理失败"]
style A fill:#fff3e0
style I fill:#c8e6c9
style J fill:#ffcdd2
流程说明:
步骤
组件
说明
1
用户提交
用户在登录页面输入用户名密码
2
Filter 拦截
UsernamePasswordAuthenticationFilter 拦截请求
3
AuthenticationManager
认证管理器,委托 AuthenticationProvider
4
AuthenticationProvider
执行具体的认证逻辑
5
UserDetailsService
根据用户名加载用户信息
6
PasswordEncoder
校验密码是否匹配
7
SecurityContext
认证成功后,保存认证信息到上下文
8-9
成功/失败处理
执行相应的处理器
4.3 核心接口与类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public interface UserDetailsService { UserDetails loadUserByUsername (String username) throws UsernameNotFoundException; } public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority > getAuthorities(); String getPassword () ; String getUsername () ; boolean isAccountNonExpired () ; boolean isAccountNonLocked () ; boolean isCredentialsNonExpired () ; boolean isEnabled () ; } public interface Authentication extends Principal { Collection<? extends GrantedAuthority > getAuthorities(); Object getCredentials () ; Object getDetails () ; Object getPrincipal () ; boolean isAuthenticated () ; void setAuthenticated (boolean isAuthenticated) throws IllegalArgumentException; } public interface GrantedAuthority extends Serializable { String getAuthority () ; }
五、用户认证详解 5.1 认证流程中的核心组件
flowchart TD
A["🕵️ 认证核心组件"] --> B["📋 Authentication\n认证凭证"]
A --> C["🗂️ UserDetails\n用户信息"]
A --> D["🔑 PasswordEncoder\n密码编码器"]
A --> E["📦 AuthenticationManager\n认证管理器"]
A --> F["🎯 AuthenticationProvider\n认证提供者"]
B --> B1["用户名\n密码\n权限列表"]
C --> C1["用户名\n密码\n账户状态\n权限"]
D --> D1["BCryptPasswordEncoder\n(推荐)\nMD5PasswordEncoder\n(不安全)"]
E --> E1["委托给\nAuthenticationProvider"]
F --> F1["执行具体\n认证逻辑"]
style A fill:#fff3e0
style B fill:#c8e6c9
style C fill:#c8e6c9
style D fill:#e3f2fd
style E fill:#fff3e0
style F fill:#f8bbd0
5.2 密码加密与存储 密码安全的重要性:
密码绝对不能明文存储!即使数据库被攻击,攻击者也无法直接获取用户密码。
推荐方案:BCrypt 加密
BCrypt 的特点:
🔐 自动加盐 :每个密码都有不同的盐值,防止彩虹表攻击
🐢 计算缓慢 :设计故意慢,增加暴力破解难度
✅ 单向加密 :无法逆向解密,只能验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Service public class PasswordService { @Autowired private PasswordEncoder passwordEncoder; public void testEncode () { String rawPassword = "myPassword123" ; String encoded1 = passwordEncoder.encode(rawPassword); String encoded2 = passwordEncoder.encode(rawPassword); System.out.println("加密结果1:" + encoded1); System.out.println("加密结果2:" + encoded2); System.out.println("验证1:" + passwordEncoder.matches(rawPassword, encoded1)); System.out.println("验证2:" + passwordEncoder.matches(rawPassword, encoded2)); } }
5.3 自定义登录接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 @Data public class LoginRequest { @NotBlank(message = "用户名不能为空") private String username; @NotBlank(message = "密码不能为空") private String password; private Boolean rememberMe; } @Data public class LoginResponse { private String token; private String username; private List<String> roles; private long expireTime; } @RestController public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtService jwtService; @PostMapping("/api/login") public Result<LoginResponse> login (@RequestBody @Valid LoginRequest request) { try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken ( request.getUsername(), request.getPassword() ); Authentication authentication = authenticationManager .authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); String token = jwtService.generateToken(userDetails); LoginResponse response = new LoginResponse (); response.setToken(token); response.setUsername(userDetails.getUsername()); response.setRoles(userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); response.setExpireTime(jwtService.getExpireTime()); return Result.success(response); } catch (BadCredentialsException e) { return Result.error(401 , "用户名或密码错误" ); } catch (DisabledException e) { return Result.error(401 , "账户已被禁用" ); } } }
5.4 多种认证方式配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .authenticationProvider(customAuthenticationProvider()) .formLogin(form -> form .loginProcessingUrl("/login" ) ) .httpBasic(basic -> basic) .oauth2Login(oauth2 -> oauth .authorizationEndpoint() .baseUri("/oauth2/authorize" ) ) .rememberMe(remember -> remember .tokenValiditySeconds(86400 ) .rememberMeParameter("remember-me" ) ); return http.build(); } }
六、授权与权限控制 6.1 基于 URL 的权限控制 最常用的权限控制方式,通过配置 URL 规则来控制访问权限:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/" , "/home" , "/login" , "/register" ).permitAll() .requestMatchers("/css/**" , "/js/**" , "/images/**" ).permitAll() .requestMatchers("/admin/**" ).hasRole("ADMIN" ) .requestMatchers("/user/**" ).hasAnyRole("USER" , "ADMIN" ) .requestMatchers("/api/public/**" ).hasAuthority("READ_PUBLIC" ) .requestMatchers("/inner/**" ).hasIpAddress("192.168.1.100" ) .anyRequest().authenticated() ); return http.build(); } }
6.2 基于方法的权限控制 在 Controller 或 Service 方法上使用注解进行权限控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Configuration @EnableMethodSecurity public class SecurityConfig { } @Service public class UserService { @PreAuthorize("hasRole('ADMIN')") public void deleteUser (Long userId) { userRepository.deleteById(userId); } @PreAuthorize("hasAuthority('USER_READ')") public User getUserById (Long id) { return userRepository.findById(id); } @PreAuthorize("#username == authentication.name or hasRole('ADMIN')") public void updateProfile (String username, String profile) { } @PreAuthorize("hasAnyAuthority('USER_READ', 'USER_WRITE')") public List<User> listUsers () { return userRepository.findAll(); } }
6.3 权限注解详解
注解
说明
示例
@PreAuthorize
方法执行前检查权限
@PreAuthorize("hasRole('ADMIN')")
@PostAuthorize
方法执行后检查权限
@PostAuthorize("returnObject.owner == authentication.name")
@Secured
基于角色的检查
@Secured("ROLE_ADMIN")
@RolesAllowed
JSR-250 标准注解
@RolesAllowed("ADMIN")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasAnyRole('ADMIN', 'USER')") @PreAuthorize("hasAuthority('USER_READ')") @PreAuthorize("hasAnyAuthorities('USER_READ', 'USER_WRITE')") @PreAuthorize("@securityService.isOwner(#userId)") @PreAuthorize("#user.name == authentication.name") @PreAuthorize("hasRole('ADMIN') or hasRole('USER')") @PreAuthorize("hasRole('ADMIN') and #age > 18")
6.4 权限不足时的异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @RestControllerAdvice public class SecurityExceptionHandler { @ExceptionHandler(AccessDeniedException.class) public Result<Void> handleAccessDenied (AccessDeniedException e) { return Result.error(403 , "权限不足,无法访问该资源" ); } @ExceptionHandler(AuthenticationException.class) public Result<Void> handleAuthentication (AuthenticationException e) { return Result.error(401 , "认证失败,请先登录" ); } @ExceptionHandler(SessionAuthenticationException.class) public Result<Void> handleSessionExpired (SessionAuthenticationException e) { return Result.error(401 , "会话已过期,请重新登录" ); } }
七、过滤器链详解 7.1 Spring Security 过滤器链 Spring Security 通过一系列过滤器链来处理安全请求:
flowchart LR
A["🔗 Security Filter Chain"] --> B["1️⃣ SecurityContext\nPersistence Filter"]
B --> C["2️⃣ WebAsyncManager\nIntegration Filter"]
C --> D["3️⃣ Header Writer Filter"]
D --> E["4️⃣ Cors Filter"]
E --> F["5️⃣ Csrf Filter"]
F --> G["6️⃣ UsernamePassword\nAuthentication Filter"]
G --> H["7️⃣ Basic Authentication Filter"]
H --> I["...\n其他过滤器"]
I --> J["8️⃣ FilterSecurity\nInterceptor"]
style A fill:#fff3e0
style B fill:#c8e6c9
style G fill:#c8e6c9
style J fill:#f8bbd0
7.2 核心过滤器详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
7.3 自定义过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Component public class IpWhiteListFilter extends OncePerRequestFilter { private static final List<String> WHITE_LIST = Arrays.asList( "127.0.0.1" , "192.168.1.100" , "localhost" ); @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String clientIp = getClientIp(request); if (WHITE_LIST.contains(clientIp)) { filterChain.doFilter(request, response); } else { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("{\"error\":\"IP not allowed\"}" ); } } private String getClientIp (HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For" ); if (ip == null || ip.isEmpty() || "unknown" .equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP" ); } if (ip == null || ip.isEmpty() || "unknown" .equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP" ); } if (ip == null || ip.isEmpty() || "unknown" .equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } } @Configuration public class SecurityConfig { @Autowired private IpWhiteListFilter ipWhiteListFilter; @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .addFilterBefore(ipWhiteListFilter, UsernamePasswordAuthenticationFilter.class) ; return http.build(); } }
7.4 过滤器执行顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .addFilterBefore(myCustomFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterAfter(myCustomFilter(), UsernamePasswordAuthenticationFilter.class) .withFilterChain(myCustomFilter(), DiscoveryFilterChain.class) ; return http.build(); } }
八、Security 实战:JWT 认证 8.1 JWT 简介 JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全传输信息 。
JWT 的结构:
flowchart LR
A["🎫 JWT 结构"] --> B["Header\n头部"]
A --> C["Payload\n载荷"]
A --> D["Signature\n签名"]
B --> B1["alg: 算法\ntyp: 类型"]
C --> C1["sub: 主题\niss: 签发者\nexp: 过期时间"]
D --> D1["HMAC SHA256\n(头部+载荷+密钥)"]
style A fill:#fff3e0
style B fill:#c8e6c9
style C fill:#e3f2fd
style D fill:#f8bbd0
8.2 JWT 工具类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 @Component public class JwtService { @Value("${jwt.secret:mySecretKeyForJwtTokenGeneration123456789}") private String secret; @Value("${jwt.expiration:86400000}") private long expiration; public String generateToken (UserDetails userDetails) { Map<String, Object> claims = new HashMap <>(); claims.put("username" , userDetails.getUsername()); claims.put("authorities" , userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date ()) .setExpiration(new Date (System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS256, secret.getBytes()) .compact(); } public String getUsernameFromToken (String token) { Claims claims = parseToken(token); return claims.getSubject(); } public long getExpireTime () { return expiration; } public boolean validateToken (String token, UserDetails userDetails) { String username = getUsernameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private boolean isTokenExpired (String token) { Date expiration = parseToken(token).getExpiration(); return expiration.before(new Date ()); } private Claims parseToken (String token) { return Jwts.parser() .setSigningKey(secret.getBytes()) .parseClaimsJws(token) .getBody(); } }
8.3 JWT 认证过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtService jwtService; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = extractJwtFromRequest(request); if (jwt != null && jwtService.validateToken(jwt)) { String username = jwtService.getUsernameFromToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken ( userDetails, null , userDetails.getAuthorities() ); authentication.setDetails( new WebAuthenticationDetailsSource ().buildDetails(request) ); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("JWT 认证失败:" + e.getMessage()); } filterChain.doFilter(request, response); } private String extractJwtFromRequest (HttpServletRequest request) { String bearerToken = request.getHeader("Authorization" ); if (bearerToken != null && bearerToken.startsWith("Bearer " )) { return bearerToken.substring(7 ); } return null ; } }
8.4 完整的 JWT 认证配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private UserDetailsService userDetailsService; @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**" ).permitAll() .requestMatchers("/api/public/**" ).permitAll() .anyRequest().authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling(ex -> ex .authenticationEntryPoint((request, response, authException) -> { response.setStatus(401 ); response.setContentType("application/json;charset=UTF-8" ); response.getWriter().write("{\"error\":\"请先登录\"}" ); }) .accessDeniedHandler((request, response, accessDeniedException) -> { response.setStatus(403 ); response.setContentType("application/json;charset=UTF-8" ); response.getWriter().write("{\"error\":\"权限不足\"}" ); }) ); return http.build(); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } }
九、OAuth2 第三方登录 9.1 OAuth2 简介 OAuth2 是一个授权框架 ,允许第三方应用在用户授权的情况下访问用户在另一个服务上的资源,而无需提供用户名密码。
flowchart TD
A["🌐 OAuth2 授权流程"] --> B["1️⃣ 用户点击\n第三方登录"]
B --> C["2️⃣ 跳转授权页面\n用户授权"]
C --> D["3️⃣ 返回授权码"]
D --> E["4️⃣ 后端用授权码\n换取 Access Token"]
E --> F["5️⃣ 用 Token\n获取用户信息"]
style A fill:#fff3e0
style C fill:#c8e6c9
style E fill:#c8e6c9
9.2 Spring Security OAuth2 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: security: oauth2: client: registration: github: client-id: your-client-id client-secret: your-client-secret scope: read:user, user:email google: client-id: your-client-id client-secret: your-client-secret scope: profile, email provider: github: authorization-uri: https://github.com/login/oauth/authorize token-uri: https://github.com/login/oauth/access_token user-info-uri: https://api.github.com/user user-name-attribute: login
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @Configuration @EnableWebSecurity public class OAuth2SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/login/**" , "/oauth2/**" ).permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint() .baseUri("/oauth2/authorize" ) ) .oauth2Client(oauth2 -> oauth2 .authorizationServer() .clientRegistrationRepository(...) ); return http.build(); } } @Component public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Autowired private UserService userService; @Override public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2AuthenticationToken authToken = (OAuth2AuthenticationToken) authentication; String provider = authToken.getAuthorizedClientRegistrationId(); String providerId = authToken.getgetName(); User user = userService.findOrCreateByOAuth2(provider, providerId); String token = jwtService.generateToken(user); response.setContentType("application/json;charset=UTF-8" ); response.getWriter().write("{\"token\":\"" + token + "\"}" ); } }
十、Spring Security 高级特性 10.1 会话管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .sessionManagement(session -> session .sessionFixation() .changeSessionId() .maximumSessions(1 ) .maxSessionsPreventsLogin(false ) .expireUrl("/login?expired" ) ); return http.build(); } }
10.2 CSRF 防护 CSRF(Cross-Site Request Forgery)跨站请求伪造,攻击者诱导用户点击链接,自动执行恶意请求。
flowchart TD
A["🚫 CSRF 攻击原理"] --> B["1️⃣ 用户已登录\n银行网站"]
B --> C["2️⃣ 用户被诱导\n点击恶意链接"]
C --> D["3️⃣ 浏览器自动发送请求\n(携带 Cookie)"]
D --> E["4️⃣ 银行误以为是\n用户操作"]
E --> F["💰 转账到攻击者账户"]
style A fill:#fff3e0
style F fill:#ffcdd2
解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .ignoringRequestMatchers("/api/**" ) ) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ) ; return http.build(); } }
10.3 密码加密选项
加密方式
说明
推荐度
BCrypt
安全可靠,自动加盐,推荐
✅✅✅
Argon2
最新标准,安全性高,Java 11+
✅✅
PBKDF2
较老但安全
✅✅
MD5/SHA1
不安全,已被淘汰
❌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class PasswordEncoderConfig { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } }
10.4 记住我功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http .rememberMe(remember -> remember .key("uniqueAndSecretKey" ) .tokenValiditySeconds(86400 * 7 ) .rememberMeParameter("remember-me" ) .useSecureCookie(false ) ); return http.build(); } }
十一、常见问题与最佳实践 11.1 常见问题与解决方案
flowchart TD
A["❓ 常见问题"] --> B["⚠️ 401 未认证"]
A --> C["⚠️ 403 权限不足"]
A --> D["⚠️ CSRF 错误"]
A --> E["⚠️ Session 过期"]
B --> B1["未登录或 Token 无效"]
B1 --> B2["检查请求头\nAuthorization"]
C --> C1["用户无权限访问"]
C1 --> C2["检查角色配置\n权限注解"]
D --> D1["CSRF Token 不匹配"]
D1 --> D2["前端添加\nCSRF Token"]
E --> E1["会话已失效"]
E1 --> E2["重新登录\n或延长超时"]
style A fill:#fff3e0
11.2 安全配置清单
安全实践
说明
推荐程度
使用 HTTPS
生产环境必须启用
✅✅✅
密码加密
使用 BCrypt 等安全算法
✅✅✅
CSRF 防护
前后端分离项目可禁用 API 端点
✅✅
XSS 防护
输入校验、输出编码
✅✅✅
SQL 注入防护
使用参数化查询
✅✅✅
JWT 短期失效
Token 过期时间不宜过长
✅✅
敏感信息脱敏
日志中不打印密码等信息
✅✅
11.3 生产环境配置建议 1 2 3 4 5 6 7 8 9 10 spring: security: oauth2: resourceserver: jwt: issuer-uri: ${JWT_ISSUER_URI}
十二、总结 12.1 核心知识点回顾
mindmap
root((Spring Security))
认证
表单登录
HTTP Basic
JWT Token
OAuth2
LDAP
授权
URL 权限控制
方法级别权限
角色 vs 权限
@PreAuthorize
过滤器
Security Filter Chain
自定义过滤器
过滤器顺序
安全防护
CSRF 防护
会话管理
密码加密
XSS 防护
高级特性
JWT 认证
OAuth2 登录
记住我
跨域配置
12.2 学习路线建议
flowchart LR
A["学习路线"] --> B["第一阶段\n基础配置"]
B --> C["第二阶段\n用户认证"]
C --> D["第三阶段\n权限控制"]
D --> E["第四阶段\nJWT 集成"]
E --> F["第五阶段\nOAuth2"]
B --> B1["基本配置\n公开路径"]
B --> B2["自定义登录"]
C --> C1["UserDetailsService\n密码加密"]
D --> D1["URL 权限\n方法权限"]
E --> E1["Token 生成\n过滤器"]
F --> F1["第三方登录\n资源服务器"]
style A fill:#fff3e0
style B fill:#e3f2fd
style C fill:#c8e6c9
style D fill:#fff3e0
style E fill:#f8bbd0
style F fill:#e3f2fd
12.3 下一步推荐学习
📖 Spring Security 源码 :深入理解框架设计
📖 OAuth2 协议规范 :深入理解授权流程
📖 Spring Cloud Security :微服务安全方案
📖 Shiro 安全框架 :对比学习另一安全框架
💡 写给读者的话 :安全是软件开发中不可忽视的重要环节。Spring Security 提供了完善的安全解决方案,但正确的配置和使用同样重要。希望本文能帮助你建立完整的安全知识体系,在实际项目中有效保护你的应用安全!🔐
📅 本文首次发布于 2026 年 5 月 24 日