Spring Boot 安全配置
1. 安全概述
1.1 Spring Security 集成
Spring Boot 自动配置 Spring Security,只需添加依赖即可启用基本安全功能。
1.2 添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>2. 基本配置
2.1 默认行为
添加依赖后,Spring Security 会:
- 启用表单登录
- 生成随机密码
- 保护所有端点
- 启用 CSRF 防护
2.2 自定义用户
yaml
spring:
security:
user:
name: admin
password: admin123
roles: ADMIN2.3 安全配置类
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/home", "/register", "/login").permitAll()
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error=true")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
)
.rememberMe(remember -> remember
.key("uniqueAndSecret")
.tokenValiditySeconds(86400)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}3. 用户认证
3.1 自定义 UserDetailsService
java
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(
"用户不存在: " + username));
return User.builder()
.username(user.getUsername())
.password(user.getPassword())
.disabled(!user.isEnabled())
.accountExpired(false)
.accountLocked(user.isLocked())
.credentialsExpired(false)
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}3.2 密码编码
java
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
public void register(UserRegistrationDto dto) {
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setEmail(dto.getEmail());
userRepository.save(user);
}
public boolean checkPassword(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}3.3 认证管理器
java
@Configuration
public class AuthenticationConfig {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}4. JWT 认证
4.1 添加依赖
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>4.2 JWT 配置
yaml
jwt:
secret: your-256-bit-secret-key-here-must-be-at-least-256-bits
expiration: 86400000
header: Authorization
prefix: Bearer4.3 JWT 工具类
java
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
public String generateToken(Authentication authentication) {
User user = (User) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.subject(user.getUsername())
.issuedAt(now)
.expiration(expiryDate)
.signWith(getSigningKey())
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}4.4 JWT 过滤器
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token != null && tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}4.5 JWT 安全配置
java
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}5. 方法级安全
5.1 启用方法安全
java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}5.2 使用方法注解
java
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public List<User> findAll() {
return userRepository.findAll();
}
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
@PreAuthorize("hasAuthority('user:create')")
public User create(User user) {
return userRepository.save(user);
}
@PostAuthorize("returnObject.username == authentication.name")
public User findByUsername(String username) {
return userRepository.findByUsername(username).orElse(null);
}
@PostFilter("filterObject.owner == authentication.name")
public List<Document> findUserDocuments() {
return documentRepository.findAll();
}
}6. OAuth2 登录
6.1 添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>6.2 OAuth2 配置
yaml
spring:
security:
oauth2:
client:
registration:
github:
client-id: your-github-client-id
client-secret: your-github-client-secret
scope: read:user, user:email
google:
client-id: your-google-client-id
client-secret: your-google-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/user6.3 OAuth2 安全配置
java
@Configuration
public class OAuth2SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login/**", "/oauth2/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error=true")
);
return http.build();
}
}7. CORS 配置
7.1 全局 CORS
java
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}7.2 Security 中启用 CORS
java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
// ...
return http.build();
}8. 异常处理
8.1 认证异常处理
java
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
Map<String, Object> data = new HashMap<>();
data.put("code", 401);
data.put("message", "未授权访问");
data.put("path", request.getRequestURI());
response.getWriter().write(new ObjectMapper().writeValueAsString(data));
}
}8.2 访问拒绝处理
java
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
Map<String, Object> data = new HashMap<>();
data.put("code", 403);
data.put("message", "权限不足");
data.put("path", request.getRequestURI());
response.getWriter().write(new ObjectMapper().writeValueAsString(data));
}
}8.3 配置异常处理
java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.exceptionHandling(exception -> exception
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
);
return http.build();
}9. 小结
本章学习了 Spring Boot 安全配置的核心内容:
| 内容 | 要点 |
|---|---|
| 基本配置 | SecurityFilterChain、用户认证 |
| 密码编码 | BCryptPasswordEncoder |
| JWT 认证 | Token 生成、验证、过滤器 |
| 方法安全 | @PreAuthorize、@PostAuthorize |
| OAuth2 | 第三方登录、配置 |
| CORS | 跨域配置 |
| 异常处理 | AuthenticationEntryPoint、AccessDeniedHandler |
下一章将学习 Spring Boot 异步处理。