Spring Security入门教程 - 从零开始学习Spring Security安全框架
Spring Security入门教程 - 从零开始学习Spring Security安全框架
目录
- Spring Security简介
- 环境搭建
- 第一个Spring Security项目
- 认证(Authentication)
- 授权(Authorization)
- 密码加密
- JWT认证
- OAuth2集成
- CSRF防护
- 方法级安全
- 自定义过滤器
- 实际应用案例
- 常见问题与最佳实践
- 总结与进阶
1. Spring Security简介
Spring Security是Spring生态系统中的一个强大且高度可定制的认证和授权框架,它为基于Spring的应用程序提供了全面的安全服务。
主要功能:
✅ 认证(Authentication):验证用户身份
✅ 授权(Authorization):控制用户访问权限
✅ 密码加密:安全的密码存储
✅ 会话管理:管理用户会话
✅ CSRF防护:防止跨站请求伪造攻击
✅ XSS防护:防止跨站脚本攻击
✅ 方法级安全:方法级别的权限控制
✅ OAuth2支持:OAuth2和JWT支持
功能强大:提供全面的安全解决方案
易于集成:与Spring框架无缝集成
高度可定制:可以根据需求定制安全策略
社区活跃:拥有活跃的社区支持
企业级:被广泛用于企业级应用
Web应用程序安全
RESTful API安全
微服务安全
单点登录(SSO)
OAuth2认证服务器
2. 环境搭建
- JDK 8或更高版本
- Maven 3.6+ 或 Gradle
- IDE(IntelliJ IDEA推荐)
- Spring Boot 2.7+ 或 3.0+
方式1:使用Spring Initializr
- 访问 https://start.spring.io/
- 选择项目配置:
- Project: Maven
- Language: Java
- Spring Boot: 3.1.0
- Group: com.example
- Artifact: spring-security-demo
- 添加依赖:
- Spring Web
- Spring Security
- 点击Generate下载项目
方式2:使用IDE创建
- IntelliJ IDEA → New → Project
- 选择Spring Initializr
- 配置项目信息
- 添加Spring Security依赖
在pom.xml中添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-security-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Security Test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JWT支持(可选) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 数据库支持(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok(可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>创建标准的项目结构:
spring-security-demo/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── SpringSecurityDemoApplication.java
│ │ │ ├── config/
│ │ │ │ └── SecurityConfig.java
│ │ │ ├── controller/
│ │ │ │ └── HomeController.java
│ │ │ ├── service/
│ │ │ │ └── UserService.java
│ │ │ ├── entity/
│ │ │ │ └── User.java
│ │ │ └── repository/
│ │ │ └── UserRepository.java
│ │ └── resources/
│ │ ├── application.properties
│ │ └── static/
│ └── test/
└── pom.xml3. 第一个Spring Security项目
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityDemoApplication.class, args);
}
}package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping("/")
public String home() {
return "Welcome to Spring Security Demo!";
}
@GetMapping("/public")
public String publicEndpoint() {
return "This is a public endpoint";
}
@GetMapping("/admin")
public String adminEndpoint() {
return "This is an admin endpoint";
}
@GetMapping("/user")
public String userEndpoint() {
return "This is a user endpoint";
}
}启动应用后,Spring Security会自动配置:
- 所有请求都需要认证
- 自动生成登录页面
- 默认用户名:
user - 默认密码:在控制台输出(每次启动不同)
访问 http://localhost:8080/ 会跳转到登录页面。
启动应用后,在控制台会看到类似输出:
Using generated security password: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx使用这个密码和用户名user登录。
4. 认证(Authentication)
认证(Authentication)是验证用户身份的过程,确认"你是谁"。
基本配置
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.build();
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("user123"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}创建User实体
package com.example.entity;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String email;
@Enumerated(EnumType.STRING)
private Role role;
private boolean enabled = true;
}
enum Role {
USER, ADMIN
}创建UserRepository
package com.example.repository;
import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}创建UserDetailsService实现
package com.example.service;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRole().name())
.disabled(!user.isEnabled())
.build();
}
}更新SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.userDetailsService(userDetailsService)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/", true)
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}创建登录页面
在src/main/resources/static/login.html:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
width: 300px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.error {
color: red;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="login-container">
<h2>Login</h2>
<div th:if="${param.error}" class="error">
Invalid username or password
</div>
<div th:if="${param.logout}" class="success">
You have been logged out
</div>
<form th:action="@{/login}" method="post">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>配置登录页面
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/", true)
.failureUrl("/login?error=true")
.permitAll()
);
return http.build();
}5. 授权(Authorization)
授权(Authorization)是控制用户访问权限的过程,决定"你能做什么"。
基于角色的授权
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 公开访问
.requestMatchers("/public/**").permitAll()
// 需要ADMIN角色
.requestMatchers("/admin/**").hasRole("ADMIN")
// 需要USER或ADMIN角色
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
// 需要特定权限
.requestMatchers("/api/read/**").hasAuthority("READ")
.requestMatchers("/api/write/**").hasAuthority("WRITE")
// 其他请求需要认证
.anyRequest().authenticated()
);
return http.build();
}基于表达式的授权
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/{id}/**")
.access("@securityService.canAccessUser(authentication, #id)")
.anyRequest().authenticated()
);
return http.build();
}启用方法安全
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// 配置
}使用注解
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
// 只有ADMIN可以删除用户
}
@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public User getUserProfile(Long userId) {
// 用户只能查看自己的资料
}
@PreAuthorize("hasAuthority('READ')")
public List<User> getAllUsers() {
// 需要READ权限
}
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) {
// 返回后检查权限
}
}创建自定义权限评估器
@Component("securityService")
public class SecurityService {
public boolean canAccessUser(Authentication auth, Long userId) {
UserDetails userDetails = (UserDetails) auth.getPrincipal();
// 管理员可以访问所有用户
if (userDetails.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
return true;
}
// 用户只能访问自己的数据
User user = userRepository.findById(userId).orElse(null);
return user != null && user.getUsername().equals(userDetails.getUsername());
}
}6. 密码加密
密码加密是安全的基础,确保即使数据库泄露,密码也不会被轻易破解。
基本使用
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 加密密码
String rawPassword = "user123";
String encodedPassword = passwordEncoder.encode(rawPassword);
// 验证密码
boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);在Service中使用
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public User createUser(String username, String rawPassword, Role role) {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(rawPassword));
user.setRole(role);
return userRepository.save(user);
}
public boolean validatePassword(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}// Argon2PasswordEncoder(推荐用于新项目)
@Bean
public PasswordEncoder passwordEncoder() {
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
}
// Pbkdf2PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
}
// SCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
}7. JWT认证
JWT(JSON Web Token)是一种开放标准,用于在各方之间安全地传输信息。
JWT由三部分组成:
- Header:算法和令牌类型
- Payload:声明(用户信息等)
- Signature:签名
创建JWT工具类
package com.example.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key-should-be-at-least-256-bits-long";
private static final long EXPIRATION_TIME = 86400000; // 24小时
private Key getSigningKey() {
return Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}创建JWT过滤器
package com.example.filter;
import com.example.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}创建认证控制器
package com.example.controller;
import com.example.util.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
public AuthController(AuthenticationManager authenticationManager,
UserDetailsService userDetailsService,
JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody LoginRequest request) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
String token = jwtUtil.generateToken(userDetails);
Map<String, String> response = new HashMap<>();
response.put("token", token);
return ResponseEntity.ok(response);
}
record LoginRequest(String username, String password) {}
}配置Security
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}8. OAuth2集成
OAuth2是一个授权框架,允许第三方应用获得对用户资源的有限访问权限。
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>配置OAuth2客户端
# application.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id: your-google-client-id
client-secret: your-google-client-secret
scope: openid,profile,email
github:
client-id: your-github-client-id
client-secret: your-github-client-secretSecurity配置
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/oauth2/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/", true)
);
return http.build();
}9. CSRF防护
CSRF(Cross-Site Request Forgery)是一种攻击方式,利用用户已登录的身份执行非预期的操作。
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}10. 方法级安全
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig {
}@Service
public class DocumentService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteDocument(Long id) {
// 只有ADMIN可以删除
}
@PreAuthorize("hasAuthority('READ')")
public Document getDocument(Long id) {
// 需要READ权限
}
@Secured("ROLE_ADMIN")
public void adminOnlyMethod() {
// 只有ADMIN可以访问
}
@PreAuthorize("#document.owner == authentication.name")
public void updateDocument(Document document) {
// 只能更新自己的文档
}
}11. 自定义过滤器
package com.example.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 前置处理
System.out.println("Before filter: " + request.getRequestURI());
filterChain.doFilter(request, response);
// 后置处理
System.out.println("After filter");
}
}@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}12. 实际应用案例
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
@PreAuthorize("hasRole('USER')")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}@Component
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String tenantId = request.getHeader("X-Tenant-ID");
TenantContext.setTenantId(tenantId);
try {
filterChain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}13. 常见问题与最佳实践
Q1: 如何禁用默认的安全配置?
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class Application {
}Q2: 如何处理CORS?
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()));
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}Q3: 如何实现记住我功能?
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.rememberMe(remember -> remember
.tokenValiditySeconds(86400) // 24小时
.key("remember-me-key")
);
return http.build();
}- 使用强密码编码器:BCrypt或Argon2
- 启用HTTPS:生产环境必须使用HTTPS
- 限制登录尝试:防止暴力破解
- 定期更新依赖:保持安全补丁最新
- 最小权限原则:只授予必要的权限
- 日志记录:记录安全相关事件
- 测试安全配置:编写安全测试用例
14. 总结与进阶
通过本教程,你已经掌握了:
- ✅ Spring Security的基本概念
- ✅ 认证和授权的实现
- ✅ 密码加密
- ✅ JWT认证
- ✅ OAuth2集成
- ✅ CSRF防护
- ✅ 方法级安全
- Spring Security源码学习
- 微服务安全
- 单点登录(SSO)
- API网关安全
- 安全测试
- 官方文档:https://spring.io/projects/spring-security
- Spring Security参考文档
- Spring Security GitHub
结语
Spring Security是一个功能强大的安全框架,通过本教程的学习,相信你已经掌握了Spring Security的核心功能和使用方法。
记住:
- 安全第一:始终将安全放在首位
- 持续学习:安全威胁不断变化,需要持续学习
- 实践为主:通过实际项目加深理解
- 关注更新:关注Spring Security的最新发展
祝你学习愉快,编程安全! 🔒
本教程由Java突击队学习社区编写,如有问题欢迎反馈。