Skip to content
清晨的一缕阳光
返回

Spring Boot Spring Security 6 核心

前言

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

支持多种编码:

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 要点:

Spring Security 6 让应用安全更加简单强大。


分享这篇文章到:

上一篇文章
MySQL 主从复制模式
下一篇文章
Java 最佳实践总结