Spring Security怎么用?

wen python案例 4

Spring Security实战指南:从零开始构建安全的Java应用

目录导读

  1. Spring Security是什么?为什么需要它?
  2. 核心概念:认证与授权
  3. 快速入门:基础配置三步走
  4. 如何保护REST API?
  5. 实战问答:常见问题与解决
  6. 进阶优化:CSRF、CORS与密码加密
  7. 安全不止于配置

Spring Security是什么?为什么需要它?

在Web开发中,安全是不可回避的核心问题,Spring Security是Spring家族中专门负责安全控制的框架,它提供认证(你是谁?)和授权(你能做什么?)两大核心功能。

Spring Security怎么用?

使用Spring Security的理由

  • 开箱即用:无需从零实现加密、会话管理、CSRF防护等底层机制。
  • 高度可定制:支持表单登录、OAuth2、LDAP、JWT等多种认证方式。
  • 社区成熟:作为Spring生态标准安全方案,文档丰富,问题反馈快。

一个例子:假设你有一个博客系统,未登录用户只能浏览文章,登录用户才能评论,管理员才能删除文章,Spring Security能让你通过声明式配置快速实现这套规则。

核心概念:认证与授权

认证 (Authentication)

验证用户身份,常见的认证方式包括:

  • 表单登录:用户输入用户名密码
  • HTTP Basic认证:请求头携带Base64编码的凭据
  • JWT Token:无状态令牌验证

授权 (Authorization)

判断用户是否有权限执行操作,常见粒度:

  • URL级别:如 /admin/** 仅允许ADMIN角色访问
  • 方法级别:如 @PreAuthorize("hasRole('USER')") 标记在Service方法上

理解“安全上下文”:Spring Security通过 SecurityContextHolder 存储当前用户信息,就像给每个请求贴了一张“身份证”。

快速入门:基础配置三步走

第一步:引入依赖

如果你的项目是Maven,在 pom.xml 添加:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

一旦添加,Spring Boot会自动配置基础安全:所有端点需要登录,默认用户名 user,密码在启动日志中随机生成。

第二步:编写安全配置类

创建一个 SecurityConfig 类,使用 @EnableWebSecurity@Configuration 注解:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                // 公开页面允许所有人访问
                .requestMatchers("/", "/home", "/login", "/register").permitAll()
                // 其他所有请求需要认证
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login") // 自定义登录页
                .defaultSuccessUrl("/dashboard", true)
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/home")
                .permitAll()
            );
        return http.build();
    }
}

第三步:添加用户存储器

默认内存用户无法满足生产需求,推荐配置JDBC或JPA用户存储:

@Bean
public UserDetailsManager users(DataSource dataSource) {
    // 使用数据库表存储用户(需预先建表)
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    return manager;
}

或者用内存用户做测试:

@Bean
public UserDetailsService users() {
    UserDetails user = User.builder()
        .username("user")
        .password("{bcrypt}加密后的密码")
        .roles("USER")
        .build();
    return new InMemoryUserDetailsManager(user);
}

关键注意:密码必须使用 PasswordEncoder 编码,推荐 BCryptPasswordEncoder

如何保护REST API?

对于前后端分离项目,Spring Security通常配合JWT(JSON Web Token)工作。

核心思路

  1. 用户登录时校验身份,生成JWT返回给前端
  2. 前端请求携带JWT到Header
  3. 自定义过滤器解析JWT并设置安全上下文

快速实现JWT过滤器示例:

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String token = extractToken(request);
        if (token != null && validateToken(token)) {
            String username = getUsernameFromToken(token);
            // 创建认证对象
            UsernamePasswordAuthenticationToken auth =
                new UsernamePasswordAuthenticationToken(username, null, List.of());
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        chain.doFilter(request, response);
    }
}

然后在配置中禁用csrf、不启用表单登录:

http.csrf(AbstractHttpConfigurer::disable)
    .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/auth/**").permitAll()
        .anyRequest().authenticated()
    )
    .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

实战问答:常见问题与解决

Q1:为什么配置了 permitAll() 但访问 /register 依然返回403?

解答:请检查你的配置顺序,Spring Security的规则生效顺序是“先匹配先生效”,若配置了 .anyRequest().authenticated() 在前面,后面permitAll将不生效,正确的顺序应把具体放行规则放在前面。

Q2:如何实现用户角色“管理员可以查看所有用户,普通用户只能查看自己”?

解答:在Controller或Service方法上使用 @PreAuthorize

@GetMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public User getUser(@PathVariable Long id) { ... }

但这需要确保认证对象包含用户ID,通常在登录时设置到 UserDetails 实现类中。

Q3:为什么我的自定义登录页面不起作用?

解答:确认两点:1) loginPage() 路径正确,且该路径已被 permitAll() 放行;2) 登录页面表单的 action 路径必须是 /login(默认),或者通过 .loginProcessingUrl() 自定义提交地址。

Q4:使用 BCryptPasswordEncoder 加密后,登录时密码如何比对?

解答:Spring Security会自动处理,你只需在配置密码时使用 {bcrypt}前缀+加密后的密文,

User.withDefaultPasswordEncoder().username("user").password("密码明文").roles("USER")

但在生产环境不推荐硬编码,应使用 PasswordEncoder Bean自动编码。

进阶优化:CSRF、CORS与密码加密

CSRF防护

  • 默认开启:对于非REST应用(表单提交)保持开启
  • API场景关闭:如使用JWT无状态认证,则需通过 http.csrf(disable()) 关闭
  • 提示:如果保留CSRF,前端每次POST/PUT/DELETE请求需携带 _csrf token,可通过Cookie或Head传递

CORS配置

前后端分离时,需允许跨域请求:

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("http://localhost:3000")); // 前端地址
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
    config.setAllowedHeaders(List.of("*"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

然后在 http 配置中启用:.cors(cors -> cors.configurationSource(corsConfigurationSource()))

密码加密策略

  • 推荐使用 BCryptPasswordEncoder,其内置随机盐值,抗彩虹表攻击
  • 配置方式:在配置类中声明 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
  • 迁移旧系统时,可组合 DelegatingPasswordEncoder 支持多种编码格式

Spring Security 的核心价值在于“通过配置将安全逻辑从业务代码中解耦”,从最简单的表单登录到复杂的OAuth2、LDAP集成,它都提供了一套可扩展的架构。

三个必须记住的原则

  1. 认证在前,授权在后:先明确用户身份,再判断其权限
  2. 显式配置匹配顺序:精确路径总是优先,通配符规则放在最后
  3. 永远不要存储明文密码:使用强哈希算法加密,配合随机盐值

最后建议:在实际项目中,先明确安全需求(哪些资源需要保护?使用什么认证方式?),再动手配置,对于初学者,从表单登录+内存用户开始,逐步替换为数据库+JWT方案,能更平稳地掌握整个框架,安全不是一次性工作,而是迭代进化的过程。

抱歉,评论功能暂时关闭!