乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

Spring Security 是什么

Spring Security 是 Spring 生态中专注于认证(Authentication)、授权(Authorization)与攻击防护的强大的可定制安全框架。它通过整合 Servlet 过滤器机制,为 Spring 应用提供了行业标准的安全保护能力。本教程默认读者已具备独立搭建 Spring Boot 或 SSM 项目的能力,以下内容聚焦于 Spring Security 本身,不再展开 Spring Core 或 Spring Boot 的基础知识。


定义与作用:为什么需要 Spring Security

在没有安全框架时,开发者通常需要为每个 Web 应用手写大量安全过滤器:检查登录状态、校验密码、判断权限、防御 CSRF、处理登出逻辑等。这些手写代码不仅重复劳动量大,而且极易因疏忽留下安全漏洞。

手写过滤器的典型痛点:

痛点说明
重复造轮子每个项目都要重写登录校验、权限判断、密码比对
安全漏洞难防CSRF、Session Fixation、Clickjacking 等攻击需要专业的防御实现
扩展困难新增 OAuth2、JWT 等认证方式时需重写整套逻辑
与 Spring 容器割裂手写 javax.servlet.Filter 无法享受 Spring 的依赖注入(DI)和 AOP 能力

Spring Security 解决了上述所有问题:它将安全能力抽象为一组可配置的过滤器链,开发者只需通过声明式配置(如 HttpSecurity)即可启用企业级安全防护,同时保留高度扩展性。


核心原理

Spring Security 的骨架可以概括为一句话:通过一条过滤器链,在请求到达 Controller 之前完成认证与授权决策,并自动附加各类攻击防护。

其核心能力围绕三大支柱展开:

  1. 认证(Authentication):验证用户身份("你是谁")。支持表单登录、HTTP Basic、JWT、OAuth2、LDAP 等多种方式。
  2. 授权(Authorization):控制用户可访问的资源("你能做什么")。支持 URL 级别、方法级别、实例级别的细粒度控制。
  3. 攻击防护:内置 CSRF 防御、Session Fixation 防护、安全响应头(防 Clickjacking、XSS 等)。

在内部实现上,Spring Security 将请求委托给 DelegatingFilterProxy,再由 FilterChainProxy 调度到匹配当前请求的 SecurityFilterChain。链中的每个过滤器各司其职,按固定顺序执行。


示例一:从手写过滤器到 Spring Security 配置

场景说明

某后台管理系统需要保护 /admin/** 路径,仅允许 ADMIN 角色的用户访问。其余路径对外开放。

操作前配置:手写过滤器方案

在没有 Spring Security 时,开发者通常手写一个过滤器:

public class AdminAuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String uri = request.getRequestURI();
        if (uri.startsWith("/admin/")) {
            HttpSession session = request.getSession(false);
            if (session == null || session.getAttribute("user") == null) {
                response.sendRedirect("/login");
                return;
            }
            // 还要手动检查角色... 代码继续膨胀
        }
        chain.doFilter(req, res);
    }
}

然后在 web.xml 中注册:

<filter>
    <filter-name>adminAuthFilter</filter-name>
    <filter-class>com.example.AdminAuthFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>adminAuthFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

痛点:角色判断、密码校验、Session 管理、CSRF 防护全部缺失;过滤器无法注入 Spring 的 Service 层查询用户。

操作后配置:Spring Security 方案

引入 Spring Security 依赖后,仅需一个配置类:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/**").permitAll()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("admin")
            .password("{noop}admin123") // 5.x 支持 {noop} 前缀
            .roles("ADMIN")
            .build());
        return manager;
    }
}

结果分析

维度手写过滤器Spring Security
代码量50+ 行,且需自行处理 Session、角色、重定向约 15 行声明式配置
安全性无 CSRF 防护,无密码编码,无 Session Fixation 防护全部默认开启
可扩展性新增 OAuth2 或 JWT 需重写整套代码替换配置即可接入新协议
Spring 集成无法注入 Service完整享受 DI、AOP、事件机制

示例二:快速启用方法级安全控制

场景说明

URL 级别的授权只能控制到路径维度。如果 /user/profile 下的"修改密码"和"查看信息"需要区分权限,就必须下沉到方法级别控制。

操作前配置

手写 AOP 切面来拦截 Service 方法,手动从 Session 中读取用户身份,再比对权限字符串:

@Aspect
@Component
public class MethodSecurityAspect {

    @Around("@annotation(com.example.RequiresRole)")
    public Object checkRole(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = 
            ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        
        RequiresRole anno = ((MethodSignature) pjp.getSignature())
            .getMethod().getAnnotation(RequiresRole.class);
        if (user == null || !Arrays.asList(anno.value()).contains(user.getRole())) {
            throw new RuntimeException("无权限");
        }
        return pjp.proceed();
    }
}

这要求自定义注解、硬编码权限判断逻辑,且无法复用 Spring Security 的 AccessDecisionManager 投票体系。

操作后配置

开启 Spring Security 的方法级安全:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 5.x 开启方法级安全
public class SecurityConfig {
    // ... 其他配置
}

在 Service 层直接加注解:

@Service
public class UserService {

    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
    public UserProfile getProfile(Long userId) {
        return userDao.findById(userId);
    }

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long userId) {
        userDao.delete(userId);
    }
}

结果分析

  • 使用 @PreAuthorize 后,权限表达式直接支持 SpEL,可以引用方法参数(#userId)和当前认证对象(authentication.principal)。
  • Spring Security 自动在方法调用前完成权限校验,失败抛出 AccessDeniedException,并由 ExceptionTranslationFilter 统一转译为 403 响应。
  • 开发者无需手写 AOP 切面,无需手动操作 Session,无需处理异常转译。

易错场景与面试考点

易错场景:混淆 hasRole 与 hasAuthority

在 Spring Security 5.x 中,hasRole("ADMIN") 会自动在内部补上前缀,实际检查的是 ROLE_ADMIN;而 hasAuthority("ADMIN") 则不做任何前缀补全,直接检查权限字符串是否精确等于 ADMIN。

错误写法:

// 如果 UserDetails 中授予的是 ROLE_ADMIN,以下写法会永久返回 403
.antMatchers("/admin/**").hasAuthority("ADMIN")

正确写法:

// 方式一:使用 hasRole,框架自动补全 ROLE_ 前缀
.antMatchers("/admin/**").hasRole("ADMIN")

// 方式二:使用 hasAuthority,手动写全权限字符串
.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")

面试考点:面试官常问 hasRole 和 hasAuthority 的区别。记住一句话:hasRole 会自动追加 ROLE_ 前缀,hasAuthority 是精确匹配。在企业代码中,建议统一使用 hasAuthority 并显式写出 ROLE_XXX,避免隐性前缀导致维护困惑。


版本说明:本章节示例基于 Spring Security 5.x 语法,使用 antMatchers 与 @EnableGlobalMethodSecurity(prePostEnabled=true) 等 5.x 典型配置风格。

上一页
本章定位
下一页
DelegatingFilterProxy