Spring Security 安全框架详解:从入门到实战 🔐

Spring Security 是 Spring 生态中用于处理认证(Authentication)和授权(Authorization)的安全框架,它强大、灵活、可扩展,几乎是所有 Java Web 应用的标配安全解决方案。本文将带你全面理解 Spring Security 的核心概念、工作原理以及实战应用!💪


📚 目录导航


一、Spring Security 概述:什么是安全框架?

1.1 为什么需要安全框架?

在 Web 应用中,我们经常会遇到以下安全问题:

常见的 Web 安全威胁:

  1. 未授权访问:用户没有登录或没有权限,却能访问某些功能
  2. 密码泄露:用户密码在传输或存储过程中被窃取
  3. CSRF 攻击:攻击者诱导用户执行非本意的操作
  4. SQL 注入:通过输入框注入恶意 SQL 语句
  5. XSS 攻击:在页面中注入恶意脚本

1.2 Spring Security 是什么?

Spring Security 是一个专注于为 Java 应用提供认证和授权的安全框架。它的主要功能包括:

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)

认证是验证用户身份的过程,通俗来说就是”确认你是谁”。

常见的认证方式:

2.2 授权(Authorization)

授权是在认证之后,根据用户的身份决定”你能做什么”。

常见的授权模型:

模型 说明 示例
RBAC 基于角色的访问控制 用户 -> 角色 -> 权限
ABAC 基于属性的访问控制 根据用户属性动态判断
DAC 自主访问控制 资源所有者自行分配权限

2.3 认证与授权的关系

典型流程:

  1. 用户登录系统 → 系统验证用户名密码 → 认证成功,发放 Token
  2. 用户访问某个功能 → 系统检查用户角色 → 判断是否有权限
  3. 有权限 → 允许访问;无权限 → 拒绝访问

三、快速入门:五分钟跑通 Spring Security

3.1 添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Spring Boot 3.x -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Spring Boot 2.x -->
<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
/**
* Spring Boot 3.x 启动类
*/
@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 // 启用 Web 安全
@EnableMethodSecurity // 启用方法级别安全(如 @PreAuthorize)
public class SecurityConfig {

/**
* 配置 HTTP 安全策略
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF(前后端分离项目通常需要禁用)
.csrf(csrf -> csrf.disable())

// 配置授权规则
.authorizeHttpRequests(auth -> auth
// 公开静态资源
.requestMatchers("/static/**", "/public/**").permitAll()
// 公开登录页面
.requestMatchers("/login", "/register").permitAll()
// admin 角色专属
.requestMatchers("/admin/**").hasRole("ADMIN")
// 其他请求需要认证
.anyRequest().authenticated()
)

// 配置表单登录
.formLogin(form -> form
.loginPage("/login") // 登录页面
.defaultSuccessUrl("/home") // 登录成功跳转
.failureUrl("/login?error") // 登录失败跳转
.permitAll()
)

// 配置 HTTP Basic 认证
.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() {
// BCrypt 是一种安全的单向加密算法,自动加盐
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
/**
* 实现 UserDetailsService 接口
* Spring Security 通过这个接口获取用户信息进行认证
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {

// 1. 根据用户名查询用户
User user = userRepository.findByUsername(username);

if (user == null) {
// 如果用户不存在,抛出异常
throw new UsernameNotFoundException("用户不存在:" + username);
}

// 2. 将用户信息转换为 Spring Security 的 UserDetails
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 核心组件架构

4.2 认证流程详解

流程说明:

步骤 组件 说明
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
/**
* Spring Security 核心接口
*/

// 1. UserDetailsService:加载用户信息
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

// 2. UserDetails:用户信息接口
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); // 权限列表
String getPassword(); // 密码
String getUsername(); // 用户名
boolean isAccountNonExpired(); // 账户是否未过期
boolean isAccountNonLocked(); // 账户是否未锁定
boolean isCredentialsNonExpired(); // 凭证是否未过期
boolean isEnabled(); // 账户是否启用
}

// 3. Authentication:认证信息接口
public interface Authentication extends Principal {
Collection<? extends GrantedAuthority> getAuthorities(); // 权限
Object getCredentials(); // 凭证(密码)
Object getDetails(); // 详情
Object getPrincipal(); // 主体(用户信息)
boolean isAuthenticated(); // 是否已认证
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

// 4. GrantedAuthority:权限接口
public interface GrantedAuthority extends Serializable {
String getAuthority(); // 权限字符串,如 "ROLE_ADMIN"
}

五、用户认证详解

5.1 认证流程中的核心组件

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";

// BCrypt 加密:每次加密结果都不同(因为随机盐)
String encoded1 = passwordEncoder.encode(rawPassword);
String encoded2 = passwordEncoder.encode(rawPassword);

System.out.println("加密结果1:" + encoded1);
System.out.println("加密结果2:" + encoded2);
// 输出示例:
// 加密结果1:$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
// 加密结果2:$2a$10$v2G8M9Qv8Q8X9Q8X9Q8X9OefgHIJKLMNOPQRSTUVWX/abcd1234

// 虽然结果不同,但验证都是 true
System.out.println("验证1:" + passwordEncoder.matches(rawPassword, encoded1)); // true
System.out.println("验证2:" + passwordEncoder.matches(rawPassword, encoded2)); // true
}
}

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;
}

/**
* 登录 Controller
*/
@RestController
public class AuthController {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private JwtService jwtService;

/**
* 用户登录
*/
@PostMapping("/api/login")
public Result<LoginResponse> login(@RequestBody @Valid LoginRequest request) {
try {
// 1. 使用 AuthenticationManager 进行认证
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
);

Authentication authentication = authenticationManager
.authenticate(authenticationToken);

// 2. 获取认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);

// 3. 生成 JWT Token
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String token = jwtService.generateToken(userDetails);

// 4. 返回响应
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) // HTTP Basic 认证
.oauth2Login(oauth2 -> oauth // OAuth2 登录
.authorizationEndpoint()
.baseUri("/oauth2/authorize")
)
.rememberMe(remember -> remember // 记住我功能
.tokenValiditySeconds(86400) // 7天
.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
// 1. 公开路径:无需认证
.requestMatchers("/", "/home", "/login", "/register").permitAll()
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()

// 2. 角色专属路径
.requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN 角色
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 或 ADMIN
.requestMatchers("/api/public/**").hasAuthority("READ_PUBLIC")

// 3. IP 白名单
.requestMatchers("/inner/**").hasIpAddress("192.168.1.100")

// 4. 其他请求需要认证
.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 {

// 需要 ADMIN 角色
@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')") // 需要 ADMIN 角色
@PreAuthorize("hasAnyRole('ADMIN', 'USER')") // 需要其中之一

// 权限检查
@PreAuthorize("hasAuthority('USER_READ')") // 需要 READ 权限
@PreAuthorize("hasAnyAuthorities('USER_READ', 'USER_WRITE')") // 需要其中之一

// 自定义安全表达式
@PreAuthorize("@securityService.isOwner(#userId)") // 调用 Bean 的方法
@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 通过一系列过滤器链来处理安全请求:

7.2 核心过滤器详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 核心过滤器说明
*/

// 1. SecurityContextPersistenceFilter
// 负责在请求开始时从 Session 加载 SecurityContext,请求结束时保存

// 2. UsernamePasswordAuthenticationFilter
// 拦截登录请求,提取用户名密码,调用 AuthenticationManager 认证
// 认证成功后创建 UsernamePasswordAuthenticationToken

// 3. BasicAuthenticationFilter
// 处理 HTTP Basic 认证,从 Authorization 头提取凭证

// 4. FilterSecurityInterceptor
// 核心过滤器,执行授权逻辑
// 根据配置决定是否允许访问

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
/**
* 自定义过滤器示例:IP 白名单过滤器
*/
@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);

// IP 白名单检查
if (WHITE_LIST.contains(clientIp)) {
// 在白名单中,直接放行
filterChain.doFilter(request, response);
} else {
// 不在白名单,返回 403
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;
}
}

/**
* 注册自定义过滤器到 Security 过滤链
*/
@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
// 1. 第一个执行:SecurityContext 持久化过滤器
// 2. 添加自定义过滤器
.addFilterBefore(myCustomFilter(), UsernamePasswordAuthenticationFilter.class)
// 3. 或者添加在某个过滤器之后
.addFilterAfter(myCustomFilter(), UsernamePasswordAuthenticationFilter.class)
// 4. 替换某个过滤器
.withFilterChain(myCustomFilter(), DiscoveryFilterChain.class)
;
return http.build();
}
}

八、Security 实战:JWT 认证

8.1 JWT 简介

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全传输信息

JWT 的结构:

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
/**
* JWT 工具类
*/
@Component
public class JwtService {

// JWT 密钥(生产环境应从配置文件读取)
@Value("${jwt.secret:mySecretKeyForJwtTokenGeneration123456789}")
private String secret;

// 过期时间(毫秒)
@Value("${jwt.expiration:86400000}") // 默认 24 小时
private long expiration;

/**
* 生成 Token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();

// 将用户名和权限列表存入 Token
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();
}

/**
* 解析 Token 获取用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();
}

/**
* 获取过期时间
*/
public long getExpireTime() {
return expiration;
}

/**
* 验证 Token
*/
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return username.equals(userDetails.getUsername())
&& !isTokenExpired(token);
}

/**
* 检查 Token 是否过期
*/
private boolean isTokenExpired(String token) {
Date expiration = parseToken(token).getExpiration();
return expiration.before(new Date());
}

/**
* 解析 Token
*/
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
/**
* JWT 认证过滤器
* 每次请求都检查 Token
*/
@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 {
// 1. 从请求头获取 Token
String jwt = extractJwtFromRequest(request);

if (jwt != null && jwtService.validateToken(jwt)) {
// 2. 解析 Token 获取用户名
String username = jwtService.getUsernameFromToken(jwt);

// 3. 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

// 4. 创建认证令牌
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);

// 5. 设置认证信息到上下文
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);

SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("JWT 认证失败:" + e.getMessage());
}

// 6. 继续执行后续过滤器
filterChain.doFilter(request, response);
}

/**
* 从请求头提取 JWT
*/
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
/**
* 完整的 Security + JWT 配置
*/
@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 -> csrf.disable())

// 配置授权规则
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll() // 公开登录接口
.requestMatchers("/api/public/**").permitAll() // 公开接口
.anyRequest().authenticated()
)

// 配置会话管理
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// JWT 认证使用无状态会话
)

// 添加 JWT 过滤器
.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 是一个授权框架,允许第三方应用在用户授权的情况下访问用户在另一个服务上的资源,而无需提供用户名密码。

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
# application.yml 配置 OAuth2
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
/**
* OAuth2 安全配置
*/
@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();
}
}

/**
* OAuth2 登录成功处理器
*/
@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);

// 生成 JWT Token(省略)
String token = jwtService.generateToken(user);

// 返回 Token
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
// 1. 会话固定攻击防护
.sessionFixation()
.changeSessionId() // 创建新会话,销毁旧会话
// 其他选项:migrateSession、none、newSession

// 2. 同一用户最大会话数
.maximumSessions(1)
.maxSessionsPreventsLogin(false) // 超过后阻止新登录

// 3. 会话超时时间
.expireUrl("/login?expired")
);

return http.build();
}
}

10.2 CSRF 防护

CSRF(Cross-Site Request Forgery)跨站请求伪造,攻击者诱导用户点击链接,自动执行恶意请求。

解决方案:

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
// 1. 启用 CSRF 防护(默认)
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // 前后端分离 API 禁用
)

// 2. 配置 CSRF Token 传递方式
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)

// 3. 前端需要从 Cookie 或 Header 中获取 CSRF Token
// 并在请求时一起发送
;

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() {
// 1. BCrypt(推荐)
return new BCryptPasswordEncoder();

// 2. Argon2(需要额外依赖)
// return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();

// 3. 多种编码器组合
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") // Token 密钥
.tokenValiditySeconds(86400 * 7) // 7 天有效期
.rememberMeParameter("remember-me") // 表单参数名
.useSecureCookie(false) // 生产环境应设为 true
);

return http.build();
}
}

十一、常见问题与最佳实践

11.1 常见问题与解决方案

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 核心知识点回顾

12.2 学习路线建议

12.3 下一步推荐学习

  • 📖 Spring Security 源码:深入理解框架设计
  • 📖 OAuth2 协议规范:深入理解授权流程
  • 📖 Spring Cloud Security:微服务安全方案
  • 📖 Shiro 安全框架:对比学习另一安全框架

💡 写给读者的话:安全是软件开发中不可忽视的重要环节。Spring Security 提供了完善的安全解决方案,但正确的配置和使用同样重要。希望本文能帮助你建立完整的安全知识体系,在实际项目中有效保护你的应用安全!🔐


📅 本文首次发布于 2026 年 5 月 24 日