Bootstrap

Spring Security的学习

一.什么是Spring Security?

官网语言:

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

翻译:

Spring Security 是一个强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实标准。 Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有 Spring 项目一样,Spring Security 的真正威力在于它能够轻松扩展以满足自定义需求。

是不是感觉不是人话?我用大白话来说一说:首先定个调,它有认证,授权,过滤器链,权限动态管理,OAuth2 第三方登录这五个技能,对,你可以想象为一个英雄有五个技能,一技能,二技能三技能,四技能,五技能。

用 ​​“英雄”​ 比喻 Spring Security,感受一下:


​一技能. 初次约会(认证:Authentication)​

简洁版技能描述:确认对方是“真人”,不是骗子。
技能使用场景举例

  • 交换基本信息:你问对方名字、年龄、职业(类似用户提交用户名密码)。
  • 验证真实性:偷偷翻对方朋友圈、问共同好友(类似 UserDetailsService 查数据库)。
  • 建立信任:确认对方信息真实后,你决定继续接触(类似生成 Authentication 对象,存入安全上下文)。

翻车场景:对方谎报年龄(密码错误)→ 直接拉黑(抛出 BadCredentialsException)。


二技能. 关系升级(授权:Authorization)​

简洁版技能描述:确定对方能解锁你的哪些“权限”。
技能使用场景举例

  • 朋友阶段:只能约饭、聊天(类似 permitAll(),基础权限)。
  • 恋人阶段:可以牵手、见朋友(类似 hasRole("LOVER"),解锁更多接口)。
  • 订婚阶段:能见家长、管工资(类似 hasAuthority("FIANCÉ"),最高权限)。

狗血剧情:对方想跳过朋友直接当恋人(访问未授权接口)→ 你果断拒绝(返回 403 错误)。


三技能. 恋爱考验(过滤器链:Filter Chain)​

简洁版技能描述:设置多道关卡,过滤不靠谱对象。
技能使用场景举例

  • 第一关:诚意检查:对方必须主动找你3次以上(类似 UsernamePasswordAuthenticationFilter,检查是否提交了登录表单)。
  • 第二关:反PUA防护:验证对方是否同时撩多人(类似 CsrfFilter,防御身份伪造)。
  • 第三关:历史审查:查对方情史是否干净(类似 SecurityContextPersistenceFilter,从“记忆”中加载用户历史行为)。

通关逻辑:任意一关不通过→直接发好人卡(终止请求),后续关卡不再执行。


四技能. 动态规则(权限动态管理)​

简洁版技能描述:关系中的权限需要灵活调整。
技能使用场景举例

  • 加分操作:对方送你机械键盘(类似新增权限 hasAuthority("GIFT")),解锁“周末宅家打游戏”权限。
  • 扣分操作:对方迟到了3次(类似权限被回收 removeAuthority("PUNCTUALITY")),禁止约周末电影。

技术对应:从数据库实时加载用户最新权限,动态判断能否访问资源。


五技能. 第三方介入(OAuth2 第三方登录)​

简洁版技能描述:通过“共同好友”快速建立信任。
技能使用场景举例

  • 找中间人:你说“我是张三的朋友”,让对方找张三确认(类似微信扫码登录,跳转第三方认证)。
  • 好友背书:张三告诉对方“这人靠谱”(第三方返回认证成功的 Token)。
  • 直接通关:对方基于张三的信任,直接给你男友权限(颁发系统访问令牌)。

二.Spring Security核心组件协作流程

       

完整恋爱流程

  1. 媒人牵线 → 2. ​导师规划阶段 → 3. ​闺蜜家长团考验 → 4. ​动态情报支持 → 5. ​危机公关 → 6. ​第三方背书

技术映射

  • 媒人 = DelegatingFilterProxy
  • 导师 = FilterChainProxy
  • 闺蜜团 = SecurityFilterChain 中的各个过滤器
  • 情报系统 = UserDetailsService + PasswordEncoder
  • 和事佬 = ExceptionTranslationFilter

1. 初次牵线(代理网关DelegatingFilterProxy)​

角色:媒人阿姨
作用

  • 她负责把男生(请求)和女生(系统)撮合到一起,但自己不直接参与恋爱流程。
  • 相当于一个“传话人”,告诉双方:“接下来你们自己聊吧!”(将请求交给 FilterChainProxy)。

经典台词
“小伙子,我只能帮你到这儿了,剩下的看你自己表现!”(转发请求给 Spring 管理的过滤器链)。


2. 恋爱阶段规划(核心调度器FilterChainProxy)​

角色:恋爱导师
作用

  • 制定一套完整的恋爱流程:先约会 → 见朋友 → 见家长(不同阶段对应不同的 SecurityFilterChain)。
  • 判断男生当前处于哪个阶段:
    • 如果男生约女生去大排档(访问 /public/**),直接放行(permitAll())。
    • 如果男生想进女生闺房(访问 /private/**),触发高难度考验(需要认证授权)。

经典台词
“追女孩子要分三步走,先吃饭,再看电影,最后见家长!”(根据 URL 匹配不同的安全规则链)。


3. 恋爱考验关卡(SecurityFilterChain)​

角色:闺蜜团 + 家长团
作用:层层把关,确保男生靠谱。每个关卡都是“过滤器”,必须按顺序通过:

第一关:身份审核(SecurityContextPersistenceFilter)​
  • 闺蜜提问:“你叫什么?哪年毕业?前女友是谁?”(从 Session 或 Token 加载用户身份)。
  • 男生操作:掏出身份证和学生证(提交 Authentication 对象)。
  • 失败后果:信息造假 → 直接拉黑(抛出 AuthenticationException)。
第二关:诚意检测(UsernamePasswordAuthenticationFilter)​
  • 家长要求:“想追我女儿?先证明你愿意为她花钱!”(检查是否提交了用户名密码)。
  • 男生操作:送花、请吃饭、买奶茶(提交登录表单)。
  • 失败后果:只送了一朵蔫玫瑰(密码错误)→ 被踢出群聊(返回登录页)。
第三关:权限升级(FilterSecurityInterceptor)​
  • 终极考验:“现在你想求婚?先有房有车!”(检查是否有 hasRole("HUSBAND") 权限)。
  • 男生操作:亮出房产证和存款(从数据库加载用户权限)。
  • 失败后果:只有自行车 → 收到好人卡(返回 403 错误)。

4. 动态信任机制(UserDetailsService + PasswordEncoder)​

角色:女生的“情报系统”

  • 闺蜜调查:通过朋友圈、微博、抖音暗中观察男生(UserDetailsService 从数据库加载用户数据)。
  • 暗号验证:两人约定“奶茶三分甜”为接头暗号(PasswordEncoder 验证密码是否匹配)。
  • 情报更新:发现男生涨工资了,自动解锁“见家长”权限(动态更新用户权限)。

5. 突发危机处理(ExceptionTranslationFilter)​

角色:和事佬

  • 场景1:男生未经允许强闯女生宿舍(未登录访问私密页面)。
    • 处理:和事佬拦住他:“先加微信再说!”(跳转到登录页)。
  • 场景2:男生试图删除女生的游戏存档(无权限操作)。
    • 处理:和事佬没收键盘:“你想挨揍吗?”(返回 403 错误页)。

6. 第三方助攻(OAuth2 Login)​

角色:共同好友

  • 场景:男生直接说:“我是张伟的朋友!”(用微信扫码登录)。
  • 流程
    1. 共同好友张伟(微信服务器)确认男生身份。
    2. 张伟告诉女生:“这人我认识,靠谱!”(颁发 OAuth2 Token)。
    3. 女生直接放行:“既然是张伟的朋友,进来吧!”(登录成功)。

技术映射

  • 媒人 = DelegatingFilterProxy
  • 导师 = FilterChainProxy
  • 闺蜜团 = SecurityFilterChain 中的各个过滤器
  • 情报系统 = UserDetailsService + PasswordEncoder
  • 和事佬 = ExceptionTranslationFilter

Spring Security 的完整流程可概括为:​请求 → 过滤器链分发 → 认证 → 上下文存储 → 授权决策 → 资源访问。其强大之处在于:

  1. 模块化设计:每个组件(如过滤器、认证器、投票器)可独立替换或扩展

  2. 与 Spring 生态无缝集成:利用 IoC 和 AOP 实现声明式配置实际开发中,只需通过 SecurityConfig 类配置核心规则,复杂的安全校验由框架自动完成

 三.Spring Security 的 authorizeRequests() 配置

    Spring Security 的 authorizeRequests() 配置中,​规则的调用顺序直接影响最终授权逻辑。其核心机制是“自上而下匹配,首次命中即生效”,类似于防火墙规则的优先级设计。以下是具体调用顺序和关键细节:

一、配置顺序的核心原则

1. 从具体到通用

正确顺序

http.authorizeRequests()
    .antMatchers("/admin/**").hasRole("ADMIN")  // 具体路径优先
    .antMatchers("/api/**").authenticated()     // 次优先级
    .anyRequest().permitAll();                  // 通用规则最后
  1. 原因:Spring Security 按配置顺序依次匹配 URL 规则,一旦匹配到某条规则,后续规则将不再处理

  2. 错误示例:若将 .anyRequest().authenticated() 放在首位,后续更具体的路径规则会被覆盖,导致所有请求均需认证
2. 匹配优先级
  • 路径匹配顺序:精确路径 > 通配符路径(如 /api/foo > /api/* > /**)。
  • 方法调用顺序
    1. antMatchers() 或 regexMatchers() 定义路径匹配规则
    2. 权限控制方法(如 hasRole()permitAll()
    3. 最终 anyRequest() 兜底规则

二、授权规则的内部处理流程

1. 请求匹配阶段
  1. URL 匹配:根据 antMatchers 定义的路径模式,按配置顺序逐一检查请求路径是否匹配

  2. 短路逻辑:首次匹配成功后,立即执行对应的权限控制逻辑,后续规则跳过

2. 权限验证阶段
  • 角色/权限验证:调用 hasRole("ADMIN") 或 hasAuthority("WRITE") 时,框架会:
    1. 从 SecurityContextHolder 获取当前用户的 Authentication 对象
    2. 检查用户权限列表中是否包含目标角色或权限

  • 动态验证:若使用 access("@customService.check(authentication, request)"),会调用自定义 Bean 方法动态决策


三、常见配置场景与陷阱

1. 混合认证与匿名访问
http.authorizeRequests()
    .antMatchers("/public/**").permitAll()  // 允许匿名访问
    .antMatchers("/user/**").hasRole("USER")  
    .anyRequest().authenticated();          // 其他路径需登录
  • 陷阱:若用户访问 /public/secret(假设未显式配置),可能被 anyRequest().authenticated() 拦截。需确保路径覆盖完整

2. 方法级与 URL 级权限冲突
  • 优先级:URL 级规则(authorizeRequests())优先于方法注解(如 @PreAuthorize

  • 建议:统一使用一种配置方式,避免规则分散导致维护困难。

四、调试与验证技巧

1. 日志追踪
  • 启用调试日志查看匹配过程:
    logging.level.org.springframework.security=DEBUG
    输出示例:
    Checking match on request '/admin/dashboard'; roles: [ADMIN] → matched
2. 断点定位
  • 关键断点位置:
    1. FilterSecurityInterceptor.beforeInvocation():观察最终授权决策逻辑

    2. AbstractSecurityInterceptor.attemptAuthorization():查看规则匹配细节


五、最佳实践

  1. 显式覆盖所有路径:避免依赖 anyRequest() 兜底导致遗漏。
  2. 角色继承:通过 RoleHierarchy 实现角色层级(如 ADMIN > USER > GUEST
  3. 最小权限原则:优先使用 hasAuthority() 而非 hasRole(),实现更细粒度控制
  4. 禁用 Session:无状态 API 中配置 sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

authorizeRequests() 的调用顺序遵循“路径由细到粗,规则由严到松”的原则。正确配置需结合业务场景,通过调试工具验证规则生效顺序,避免因优先级错误导致安全漏洞。实际开发中,建议结合 @PreAuthorize 注解实现方法级细粒度控制,与 URL 级规则互补

​四、密码加密器的使用

1. 配置密码加密器

Spring Security 推荐使用 BCryptPasswordEncoder 进行密码加密,需在配置类中声明为 Bean:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(10); // 参数为加密强度默认10
2. 加密密码

在用户注册或修改密码时,调用 encode 方法加密明文密码:

@Autowired
private PasswordEncoder passwordEncoder;

public void registerUser(String username, String rawPassword) {
    String encodedPassword = passwordEncoder.encode(rawPassword);
    saveToDatabase(username, encodedPassword); // 存储加密后的密码
3. 密码验证

用户登录时,Spring Security 自动调用 matches 方法验证密码:

// 自动处理流程(无需手动调用)
boolean isMatch = passwordEncoder.matches(rawPassword, storedEncodedPassword);
4. 其他加密器
  • Pbkdf2PasswordEncoder:适用于需要高计算成本的场景。
  • SCryptPasswordEncoder:抗硬件破解,适合敏感数据。
  • NoOpPasswordEncoder​(仅测试用):明文存储,不推荐生产环境。

二、获取当前用户信息

1. 通过 SecurityContextHolder

直接获取 Authentication 对象,适用于非匿名场景:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
List<GrantedAuthority> roles = (List<GrantedAuthority>) authentication.getAuthorities(); 
2. 使用 @AuthenticationPrincipal 注解

在控制器方法中注入自定义的 UserDetails 对象:

@GetMapping("/user")
public String currentUser(@AuthenticationPrincipal CustomUser user) {
    return user.getUsername(); // 需实现 UserDetails 接口
3. 通过 Principal 参数

直接获取用户身份信息:

@GetMapping("/username")
public String getUsername(Principal principal) {
    return principal.getName(); // 返回用户名[7,8](@ref)
4. 使用 @CurrentSecurityContext 注解(Spring Security 5+)​

灵活获取安全上下文中的属性:

@GetMapping("/principal")
public String getPrincipal(@CurrentSecurityContext(expression = "authentication.principal") 
                           Principal principal) {
    return principal.getName(); // 支持 SpEL 表达式[7](@ref)

三、Security 零散配置

1. 登录与权限配置
  • 自定义登录页
    http.formLogin()
        .loginPage("/login")       // 自定义登录页路径
        .loginProcessingUrl("/doLogin") // 表单提交地址,要与控制层相应地址匹配
  • 权限规则
    http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")  // 管理员权限
        .antMatchers("/public/**").permitAll()      // 公开接口
2. 异常处理
  • 认证异常:跳转登录页或返回 401:
    http.exceptionHandling()
        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
  • 权限不足:自定义 403 处理:
    http.exceptionHandling()
        .accessDeniedHandler(new CustomAccessDeniedHandler())
3. 会话与 CSRF
  • 会话管理:限制并发登录数:
    http.sessionManagement()
        .maximumSessions(1) // 同一用户最多1个会话
  • 禁用 CSRF​(仅限无状态 API):
    http.csrf().disable();
4. 其他配置
  • 退出登录
    http.logout()
        .logoutUrl("/logout")           // 退出路径
        .logoutSuccessUrl("/login")     // 退出后跳转页
  • 多过滤器链:按 URL 划分不同安全规则:
    @Bean
    @Order(1)
    SecurityFilterChain apiFilterChain(HttpSecurity http) {
        http.requestMatchers("/api/**"); // 仅处理 API 

总结

  1. 密码加密器:优先使用 BCryptPasswordEncoder,通过 encode 和 matches 实现加密与验证

  2. 用户信息获取:根据场景选择 SecurityContextHolder、注解或参数注入

  3. 零散配置:涵盖登录、权限、异常、会话等,通过 HttpSecurity 灵活组合

五. springboot集成Security,Thymeleaf Spring Security集成(注意:兄弟们,我写的这份代码仅供理解Security文章使用,具体代码需要结合具体业务逻辑!!!

一、基础集成步骤

1. 添加依赖

在 pom.xml 中引入 相关依赖

<properties>
        <java.version>17</java.version>
        <mybatis-spring-boot.version>3.0.3</mybatis-spring-boot.version>
        <thymeleaf-extras-springsecurity.version>3.1.2.RELEASE</thymeleaf-extras-springsecurity.version>
    </properties>
<dependencies>
<!-- Spring Boot Security核心依赖,用于处理安全性 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Spring Security测试依赖 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Thymeleaf Spring Security集成依赖 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
            <version>${thymeleaf-extras-springsecurity.version}</version>
        </dependency>

        <!-- Thymeleaf模板引擎依赖,用于构建前端页面 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- 数据校验相关依赖,用于验证输入数据的有效性 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
</dependencies>

添加后,所有接口默认会被保护,需认证才能访问。

扩展:

  1. Docker 容器化

    • 镜像构建:Dockerfile 打包 Spring Boot Jar 包 + Nginx 前端资源
    • 集群部署:Kubernetes 管理微服务实例
2. 默认安全行为
  • 自动配置:Spring Boot 会启用默认安全规则,所有 HTTP 请求需认证

  • 默认用户:用户名为 user,密码在启动日志中随机生成(如 Using generated security password: 1234-5678

3. 自定义用户认证

方式1:配置文件
在 application.properties 中配置静态用户:

spring.security.user.name=admin
spring.security.user.password=123456
spring.security.user.roles=ADMIN

方式2:内存认证
创建配置类,覆盖 UserDetailsService

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .permitAll()
            )
            .logout(logout -> logout
                .permitAll()
            );
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("123"))
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();  // 必须配置密码加密器
    }
}

二、高级配置

1. 权限控制

通过 HttpSecurity 配置 URL 访问规则

http.authorizeRequests()
    .antMatchers("/admin/**").hasRole("ADMIN")      // 仅管理员可访问
    .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") 
    .antMatchers("/login", "/register").permitAll() // 公开接口
    .anyRequest().authenticated();
2. 数据库动态用户认证

实现 UserDetailsService 从数据库加载用户

@Service
public class CustomUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList())
        );
    }
}
3. 密码加密

强制使用 BCryptPasswordEncoder 加密存储密码

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();  // 推荐强度因子 10-12
}

三、扩展功能

1. 自定义登录页

禁用默认登录页,指定自定义页面

http.formLogin(form -> form
    .loginPage("/login")     // 自定义登录页路径
    .loginProcessingUrl("/doLogin")  // 表单提交地址
    .defaultSuccessUrl("/home", true)
);
2. 防御攻击
  • CSRF 防护:默认启用,若需禁用(如 API 服务):
    http.csrf().disable();  // 慎用,仅限无状态场景
  • 会话管理:限制同一用户并发会话数:
    http.sessionManagement()
        .maximumSessions(1)
        .expiredUrl("/login?expired");
3. 集成 JWT(无状态认证)​

添加 JWT 过滤器替代 Session 认证

http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

四、测试与验证

1. 接口测试
  1. 未认证请求:返回 302 重定向到 /login(浏览器)或 401(Postman)
  2. 认证请求:在 Header 中添加 Authorization: Basic base64(username:password)
2. 日志调试

开启 Spring Security 调试日志:

logging.level.org.springframework.security=DEBUG

总结

通过以上步骤,Spring Boot 可快速集成 Spring Security 实现以下能力:

  1. 开箱即用:默认保护所有接口,简化基础安全配置

  2. 灵活扩展:支持内存、数据库、LDAP 等多种认证源

  3. 生产级防护:内置 CSRF、XSS 等攻击防御

  4. 无状态支持:结合 JWT 或 OAuth2 适配微服务架构

 

;