Spring Boot 3整合Spring Security 6全攻略:从零构建安全防线

Spring Boot 3整合Spring Security 6全攻略:从零构建安全防线

#SpringSecurity #SpringBoot3 #安全认证 #JWT

一、环境准备与版本匹配

技术栈选型

组件 版本 必要性说明
Spring Boot 3.2.0+ 要求JDK17+
Spring Security 6.2.0+ 全新配置链式API
Java 17+ Records特性优化DTO封装
Lombok 1.18.30 简化实体类开发


    org.springframework.boot
    spring-boot-starter-security


    com.baomidou
    mybatis-plus-spring-boot3-starter
    {version}


    com.mysql
    mysql-connector-j
    runtime

二、基础安全配置四步曲

1. 安全配置类(Lambda DSL新写法)

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests
                                .requestMatchers("/api/auth/login").permitAll()
                                .anyRequest().authenticated()
                )
                .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService),
                        UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:8080/"); // 允许的源
        config.setAllowCredentials(true); // 是否允许发送凭证(如 cookies)
        config.addAllowedMethod("*"); // 允许的方法
        config.addAllowedHeader("*"); // 允许的头
        config.addExposedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

三、数据库用户认证实战

1. 用户实体类设计

@Getter
@Setter
@ToString
public class User implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private String id;

    /**
     * 用户名称
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    @TableField(exist = false)
    private List<String> roleIdList;

    /**
     * 账户是否未过期(1未过期 0已过期)
     */
    private boolean isAccountNonExpired = true;

    /**
     * 账户是否未锁定(1未锁定 0已锁定)
     */
    private boolean isAccountNonLocked = true;

    /**
     * 密码是否未过期(1未过期 0已过期)
     */
    private boolean isCredentialsNonExpired = true;

    /**
     * 账户是否可用(1可用 0不可用)
     */
    private boolean isEnabled = true;

    // 实现UserDetails接口方法
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
                .map(SimpleGrantedAuthority::new)
                .toList();
    }
}

2. 自定义UserDetailsService

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.selectUserByUserName(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return user;
    }
}

四、JWT整合方案(REST API安全)

1. JWT工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.blog.common.enums.ResultCode;
import com.blog.common.exception.CommonException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;

@Component
public class JwtUtil {

    @Value("${jwt.issuer}")
    private String issuer;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private int expiration;

    public String generateToken(Map<String, String> map) {
        //设置令牌的过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, expiration);
        JWTCreator.Builder builder = JWT.create();
        map.forEach(builder::withClaim);
        return builder.withIssuer(issuer)
                .withIssuedAt(new Date())
                .withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(secret));
    }

    /**
     * 解析领陪
     *
     * @param token
     * @return
     */
    public DecodedJWT jwtDecode(String token) {
        try {
            return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
        } catch (SignatureVerificationException e) {
            throw new CommonException(ResultCode.TOKEN_SIGNATURE_ERROR);
        } catch (AlgorithmMismatchException e) {
            throw new CommonException(ResultCode.TOKEN_ALGORITHM_MISMATCH);
        } catch (TokenExpiredException e) {
            throw new CommonException(ResultCode.TOKEN_EXPIRED);
        } catch (Exception e) {
            throw new CommonException(ResultCode.TOKEN_ERROR);
        }
    }

    /**
     * 校验令牌
     *
     * @param token
     * @return
     */
    public boolean validateToken(String token) {
        try {
            JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
        } catch (JWTVerificationException e) {
            return false;
        }
        return true;
    }

    // 验证 Token 并提取用户名
    public String getUsernameFromToken(String token) {
        DecodedJWT decodedJWT = jwtDecode(token);
        return decodedJWT.getClaim("username").asString();
    }
}

2. JWT认证过滤器

import com.blog.common.utils.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.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 {
        String token = extractToken(request);
        if (token != null && jwtUtil.validateToken(token)) {
            String username = jwtUtil.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 extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

五、安全测试与调试技巧

1. 接口测试

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

    @GetMapping("/login")
    public LoginVo login(@RequestBody LoginDto loginDto) {
       UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
        // 认证
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // 保存认证状态
        SecurityContextHolder.getContext().setAuthentication(authenticate);

        //获取用户信息
        User user = (User) authenticate.getPrincipal();
        LoginVo loginVo = new LoginVo();
        loginVo.setId(user.getId());
        
        Map<String, String> map = new HashMap<>();
        map.put("id", user.getId());
        map.put("username", user.getUsername());
        String token = jwtUtil.generateToken(map);
        
        loginVo.setToken(token);
        return loginVo;
    }
}

2. 安全日志监控配置

# application.properties
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.web=INFO

六、生产环境加固建议

  1. 密码存储:必须使用BCrypt/SCrypt等强哈希算法
  2. HTTPS强制:配置server.ssl.enabled=true
  3. CSRF保护:正式环境务必启用
  4. 速率限制:集成Spring Boot Starter Actuator监控
  5. 安全头配置
http.headers(headers -> headers
    .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
    .httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true))
);

你可能感兴趣的:(spring,spring,boot,安全)