🎬 HoRain云小助手个人主页

 🔥 个人专栏: 《Linux 系列教程》《c语言教程

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

专栏介绍

专栏名称

专栏介绍

《C语言》

本专栏主要撰写C干货内容和编程技巧,让大家从底层了解C,把更多的知识由抽象到简单通俗易懂。

《网络协议》

本专栏主要是注重从底层来给大家一步步剖析网络协议的奥秘,一起解密网络协议在运行中协议的基本运行机制!

《docker容器精解篇》

全面深入解析 docker 容器,从基础到进阶,涵盖原理、操作、实践案例,助您精通 docker。

《linux系列》

本专栏主要撰写Linux干货内容,从基础到进阶,知识由抽象到简单通俗易懂,帮你从新手小白到扫地僧。

《python 系列》

本专栏着重撰写Python相关的干货内容与编程技巧,助力大家从底层去认识Python,将更多复杂的知识由抽象转化为简单易懂的内容。

《试题库》

本专栏主要是发布一些考试和练习题库(涵盖软考、HCIE、HRCE、CCNA等)

目录

⛳️ 推荐

专栏介绍

🔑 深入认证流程

🛡️ 深入授权流程

💻 实践指南与最佳实践

1. 基础配置示例

2. 方法级授权

3. 最佳实践


img

Spring Security 是 Spring 生态中负责安全的框架,其核心功能认证(Authentication)​ 和 授权(Authorization)​ 通过一系列精密的组件协作完成。简单来说,认证解决“你是谁”的问题,而授权则判断“你能做什么”​ 。

为了让你快速建立整体认知,下图清晰地描绘了一个请求从抵达至被处理的完整安全流程,它本质上是一个过滤器链:

flowchart TD
A[HTTP请求] --> B[过滤器链]
B --> C[SecurityContextPersistenceFilter<br>加载SecurityContext]
C --> D{是否为认证请求?}
D -- 是<br>如/login --> E[认证过滤器<br>如UsernamePasswordAuthenticationFilter]
E --> F[AuthenticationManager<br>认证入口]
F --> G[AuthenticationProvider<br>执行具体认证逻辑]
G --> H[UserDetailsService<br>加载用户信息]
H --> I[PasswordEncoder<br>密码校验]
I --> J{认证成功?}
J -- 是 --> K[构建Authentication对象<br>并存入SecurityContextHolder]
J -- 否 --> L[抛出AuthenticationException]
L --> M[ExceptionTranslationFilter<br>处理异常<br>如返回401]
K --> N[FilterSecurityInterceptor<br>执行授权检查]
N --> O[SecurityMetadataSource<br>获取资源所需权限]
O --> P[AccessDecisionManager<br>进行授权决策]
P --> Q{有权访问?}
Q -- 是 --> R[访问目标资源]
Q -- 否 --> S[抛出AccessDeniedException]
S --> T[ExceptionTranslationFilter<br>处理异常<br>如返回403]

🔑 深入认证流程

认证的目的是确认用户的身份。其核心步骤与上图的左侧部分对应:

  1. 请求拦截与凭证提取:当用户提交登录请求(如访问 /login),UsernamePasswordAuthenticationFilter会拦截该请求,并从请求中提取出用户名和密码,将其封装成一个未认证的 UsernamePasswordAuthenticationToken对象 。

  2. 认证逻辑执行:这个未认证的 Token 被传递给 AuthenticationManager(其默认实现是 ProviderManager)。ProviderManager会遍历一个 AuthenticationProvider列表,找到一个能够处理该类型 Token 的 Provider(通常是 DaoAuthenticationProvider)来执行实际认证 。

  3. 加载用户信息与密码校验DaoAuthenticationProvider会调用我们自定义的 UserDetailsService实现。UserDetailsServiceloadUserByUsername(String username)方法负责从数据库或其他数据源中根据用户名加载用户详细信息,并返回一个 UserDetails对象 。接着,DaoAuthenticationProvider会使用配置的 PasswordEncoder(如 BCryptPasswordEncoder)来比对用户输入的密码和 UserDetails中存储的已加密密码 。

  4. 认证结果处理

    • 成功:认证成功后,AuthenticationProvider会返回一个包含用户信息和权限(GrantedAuthority)的已认证的 Authentication对象。该对象会被设置到 SecurityContextHolder中,这意味着用户在此次会话(或请求)中已被视为登录状态 。

    • 失败:如果任何一步出错(如用户不存在、密码错误),则会抛出特定的 AuthenticationException,并由 ExceptionTranslationFilter捕获,最终返回 401 等错误状态 。


🛡️ 深入授权流程

授权发生在认证之后,决定已认证的用户是否有权限访问某个资源。其核心步骤与上图的右侧部分对应:

  1. 触发授权检查:当用户访问一个受保护的资源(如 /admin)时,请求会到达过滤器链末端的 FilterSecurityInterceptor

  2. 获取权限规则FilterSecurityInterceptor会通过 SecurityMetadataSource查询访问该资源所需的权限(例如,访问 /admin/**需要 ROLE_ADMIN)。这些规则通常在我们的 SecurityConfig配置类中通过 http.authorizeRequests()进行定义 。

  3. 授权决策FilterSecurityInterceptor调用 AccessDecisionManager来做出最终的授权决策。AccessDecisionManager本身不直接做决定,而是依赖于一组 AccessDecisionVoter进行“投票” 。Spring Security 提供了三种主要的决策策略:

    • AffirmativeBased(默认):只要有一个投票器通过即放行 。

    • ConsensusBased:多数投票器通过则放行 。

    • UnanimousBased:要求所有投票器一致通过才放行 。

      常见的 RoleVoter会检查 Authentication对象中的权限是否包含资源所需的权限 。

  4. 决策结果处理

    • 通过:请求被放行,访问目标资源。

    • 拒绝:如果权限不足,AccessDecisionManager会抛出 AccessDeniedException,同样由 ExceptionTranslationFilter捕获,通常会返回 403 错误 。


💻 实践指南与最佳实践

1. 基础配置示例

以下是一个整合了认证和授权的配置类示例,它展示了如何保护不同的URL路径:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()     // 公开访问
                .requestMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色
                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 需要USER或ADMIN角色
                .anyRequest().authenticated() // 其他所有请求需要认证
            )
            .formLogin(form -> form
                .loginPage("/login")          // 自定义登录页面
                .defaultSuccessUrl("/home")   // 登录成功后的默认跳转页面
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/login?logout") 
                .permitAll()
            );
        return http.build();
    }

    // 配置用户详情服务(此处为内存演示,生产环境需连接数据库)
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder().encode("admin"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

    // 密码编码器,必须配置
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
2. 方法级授权

除了在Web层面进行授权,还可以在Service层的方法上进行更细粒度的控制。首先需要启用该功能:

@Configuration
@EnableMethodSecurity(prePostEnabled = true) // 启用方法级安全控制
public class MethodSecurityConfig {
}

然后,可以直接在方法上使用注解:

@Service
public class BookService {
    
    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
    public void deleteUserBook(Long userId, String bookId) {
        // 只有管理员或资源所有者本人可以执行此操作
    }
    
    @PostAuthorize("returnObject.owner == authentication.name")
    public Book getBookDetails(String bookId) {
        // 方法执行后检查,确保用户只能访问属于自己的书籍详情
    }
}
3. 最佳实践
  • 密码加密:永远使用强大的 PasswordEncoder(如 BCryptPasswordEncoder)来加密存储密码,切勿使用明文 。

  • 最小权限原则:只授予用户完成其工作所必需的最小权限。

  • 动态加载权限:对于复杂系统,应从数据库动态加载权限规则,而非在代码中写死。这通常通过自定义 UserDetailsService实现,在 loadUserByUsername方法中关联查询用户的角色和权限 。

  • 正确处理异常:自定义 AuthenticationEntryPointAccessDeniedHandler以给前端返回统一的、用户友好的错误信息 。

希望这份详细的流程解析和实践指南能帮助你更好地理解和应用 Spring Security。如果你对某个特定环节(如集成JWT、OAuth2.0)有进一步的兴趣,我们可以继续深入探讨。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐