口袋勇气免DVD硬盘版
4.9G · 2025-09-18
Spring Security是Spring生态中最强大的安全框架,随着Spring Security 6.x的发布,它提供了更现代化的安全特性和更简洁的配置方式。本指南将详细介绍如何将Spring Security 6.x集成到Spring Boot应用中,涵盖从基础配置到高级特性的完整实现。
在开始集成Spring Security 6.x之前,需要确保开发环境满足以下要求:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
如果使用Gradle,则在build.gradle中添加:
implementation 'org.springframework.boot:spring-boot-starter-security'
可选依赖:根据项目需求,可能需要添加以下扩展依赖:
io.jsonwebtoken:jjwt-api
spring-boot-starter-oauth2-client
spring-boot-starter-data-jpa
确保所有依赖版本兼容,推荐使用Spring Boot的依赖管理机制自动管理版本。
Spring Security 6.x的一个重大变化是废弃了传统的WebSecurityConfigurerAdapter
类,改为使用组件化的配置方式。
创建安全配置类,定义SecurityFilterChain
Bean:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 生产环境应开启
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
}
关键点说明:
authorizeHttpRequests
替代了旧的antMatchers
,用于定义URL访问规则Spring Security 6.x强制要求使用密码编码器,推荐使用BCrypt:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
此编码器将用于所有密码的加密存储和验证。
对于开发和测试环境,可以配置内存用户:
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
注意:生产环境不应使用内存用户和默认密码编码器。
实际生产环境中,用户信息通常存储在数据库中。以下是实现数据库认证的步骤:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
// getters and setters
}
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
@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"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRole())
.build();
}
}
此服务将从数据库加载用户信息并转换为Spring Security识别的UserDetails对象。
确保用户注册时密码经过加密:
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public UserService(PasswordEncoder passwordEncoder, UserRepository userRepository) {
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
}
public void registerUser(UserRegistrationDto registrationDto) {
User user = new User();
user.setUsername(registrationDto.getUsername());
user.setPassword(passwordEncoder.encode(registrationDto.getPassword()));
user.setRole("USER");
userRepository.save(user);
}
}
前后端分离架构通常采用JWT(JSON Web Token)实现无状态认证。
创建JWT生成和验证的工具类:
@Component
public class JwtTokenProvider {
private final String secretKey = "your-secret-key"; // 应从配置读取
private final long validityInMilliseconds = 86400000; // 24小时
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = // 从token中解析用户信息并创建UserDetails
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token is expired or invalid");
}
}
}
创建过滤器处理JWT令牌:
public class JwtTokenFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
修改安全配置:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint);
return http.build();
}
关键配置:
SessionCreationPolicy.STATELESS
addFilterBefore
Spring Security 6.x提供了灵活的授权机制,支持URL级别和方法级别的权限控制。
在安全过滤链中配置:
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
启用方法安全并添加注解:
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
// 可选自定义配置
}
然后在服务方法上使用注解:
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
public User getUser(String username) {
// 只有ADMIN或用户自己可以获取用户信息
}
@Secured("ROLE_ADMIN")
public void deleteUser(Long id) {
// 只有ADMIN可以删除用户
}
}
支持的注解包括:
@PreAuthorize
:方法执行前检查@PostAuthorize
:方法执行后检查@Secured
:简单的角色检查对于更复杂的权限需求,可以从数据库加载权限:
public class DynamicPermissionService {
public List<GrantedAuthority> loadPermissions(Long userId) {
// 从数据库查询用户权限
return permissions.stream()
.map(perm -> new SimpleGrantedAuthority("ROLE_" + perm.getCode()))
.collect(Collectors.toList());
}
}
然后在UserDetailsService中设置权限:
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
List<GrantedAuthority> authorities = permissionService.loadPermissions(user.getId());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
authorities
);
}
Spring Security 6.x简化了OAuth2客户端的配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
spring:
security:
oauth2:
client:
registration:
google:
client-id: your-client-id
client-secret: your-client-secret
scope: profile,email
http.rememberMe(remember -> remember
.key("uniqueAndSecret")
.tokenValiditySeconds(1209600) // 14天
.userDetailsService(userDetailsService)
);
http.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true) // 阻止新登录
);
http.headers(headers -> headers
.contentSecurityPolicy("default-src 'self'")
.xssProtection(XssProtectionConfigurer::block)
);
使用@WithMockUser
注解模拟认证用户:
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void testAdminAccess() throws Exception {
mockMvc.perform(get("/api/admin"))
.andExpect(status().isOk());
}
监听认证事件:
@EventListener
public void onAuthenticationSuccess(AuthenticationSuccessEvent event) {
logger.info("User {} logged in from {}",
event.getAuthentication().getName(),
event.getAuthentication().getDetails());
}
WebSecurityConfigurerAdapter已弃用:
使用SecurityFilterChain
Bean替代
版本兼容性问题:
确保Spring Boot 3.x与Spring Security 6.x版本匹配
CSRF保护问题:
前后端分离项目可禁用CSRF,传统Web应用应开启
密码编码器缺失:
必须配置PasswordEncoder Bean
JWT验证失败:
检查令牌签名算法和密钥是否一致
密码安全:
最小权限原则:
日志与监控:
定期更新: