前言
Spring Security 是 Java 领域最流行的安全框架。Spring Security 6 带来了全新的安全配置方式。本文将介绍 Spring Security 6 的核心概念和实战用法。
快速开始
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 基础配置
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**", "/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config
) throws Exception {
return config.getAuthenticationManager();
}
}
3. 用户详情服务
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(
"用户不存在:" + username
));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}
认证管理
1. 用户名密码认证
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/home")
.failureUrl("/login?error")
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
);
return http.build();
}
}
2. JWT 认证
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
if (username != null &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails =
userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
3. 配置 JWT 过滤器
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(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(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
授权管理
1. 基于角色授权
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 公开接口
.requestMatchers("/api/public/**").permitAll()
// 需要认证
.requestMatchers("/api/user/**").authenticated()
// 需要特定角色
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/manager/**").hasAnyRole("ADMIN", "MANAGER")
// 需要特定权限
.requestMatchers("/api/user/create").hasAuthority("user:create")
.requestMatchers("/api/user/delete").hasAuthority("user:delete")
// 其他需要认证
.anyRequest().authenticated()
);
return http.build();
}
}
2. 方法级别授权
@Service
public class UserService {
/**
* 需要认证
*/
@PreAuthorize("isAuthenticated()")
public UserDTO getCurrentUser() {
// ...
}
/**
* 需要角色
*/
@PreAuthorize("hasRole('ADMIN')")
public List<UserDTO> getAllUsers() {
// ...
}
/**
* 需要权限
*/
@PreAuthorize("hasAuthority('user:delete')")
public void deleteUser(Long id) {
// ...
}
/**
* 基于数据授权
*/
@PreAuthorize("#userId == authentication.principal.id")
public UserDTO getUserById(Long userId) {
// ...
}
}
3. 自定义权限评估器
@Component("customPermissionEvaluator")
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(
Authentication auth,
Object targetDomainObject,
Object permission
) {
if (auth == null || targetDomainObject == null) {
return false;
}
String targetType = targetDomainObject.getClass().getSimpleName();
UserPrincipal principal = (UserPrincipal) auth.getPrincipal();
return principal.hasPermission(targetType, permission.toString());
}
@Override
public boolean hasPermission(
Authentication auth,
Serializable targetId,
String targetType,
Object permission
) {
// 基于 ID 的权限检查
return false;
}
}
@Service
public class DocumentService {
@PreAuthorize("hasPermission(#document, 'READ')")
public Document readDocument(Document document) {
return document;
}
@PreAuthorize("hasPermission(#document, 'WRITE')")
public void updateDocument(Document document) {
// ...
}
}
密码编码
1. 使用 BCrypt
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public User create(UserCreateDTO dto) {
User user = new User();
user.setUsername(dto.getUsername());
// 密码加密
user.setPassword(passwordEncoder.encode(dto.getPassword()));
return userRepository.save(user);
}
public boolean checkPassword(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
2. 多种编码方式
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
支持多种编码:
{bcrypt}- BCrypt(默认){noop}- 明文{pbkdf2}- PBKDF2{scrypt}- SCrypt{argon2}- Argon2
CSRF 防护
1. 启用 CSRF
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
2. 自定义 CSRF Token
@Component
public class CsrfTokenFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
CsrfToken token = (CsrfToken) httpRequest.getAttribute(CsrfToken.class.getName());
if (token != null) {
// 预加载 Token
token.getToken();
}
chain.doFilter(request, response);
}
}
会话管理
1. 无状态会话
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
2. 有状态会话
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.maximumSessions(1) // 最大会话数
.maxSessionsPreventsLogin(false) // 是否阻止新登录
.expiredUrl("/login?expired") // 过期跳转
)
.sessionFixation().migrateSession() // 会话固定保护
.invalidSessionUrl("/login?invalid"); // 无效会话跳转
return http.build();
}
3. 并发控制
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true) // 阻止新登录
);
return http.build();
}
最佳实践
1. 安全配置
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF(API 使用 Token)
.csrf(csrf -> csrf.disable())
// 禁用 Session
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 授权配置
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// 禁用缓存
.headers(headers -> headers
.cacheControl(cache -> cache.disable())
)
// 添加 JWT 过滤器
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 增加强度
}
}
2. 密码策略
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
public boolean validatePassword(String password) {
// 至少 8 位
if (password.length() < 8) {
return false;
}
// 包含大小写字母和数字
if (!password.matches(".*[A-Z].*") ||
!password.matches(".*[a-z].*") ||
!password.matches(".*\\d.*")) {
return false;
}
return true;
}
3. 异常处理
@RestControllerAdvice
public class SecurityExceptionHandler {
@ExceptionHandler(AuthenticationException.class)
public R<Void> handleAuthenticationException(AuthenticationException e) {
return R.error(401, "认证失败:" + e.getMessage());
}
@ExceptionHandler(AccessDeniedException.class)
public R<Void> handleAccessDeniedException(AccessDeniedException e) {
return R.error(403, "无权访问");
}
}
总结
Spring Security 要点:
- ✅ 认证 - 用户名密码、JWT
- ✅ 授权 - 角色、权限、方法级别
- ✅ 密码编码 - BCrypt、多种编码
- ✅ CSRF 防护 - Token 机制
- ✅ 会话管理 - 无状态、并发控制
- ✅ 最佳实践 - 安全配置、密码策略
Spring Security 6 让应用安全更加简单强大。