人人都会AI编程

15.1 认证与授权核心架构

更新时间:2026-07-11

在任何一个面向用户的系统中,安全始终是无法绕开的话题。Spring Security 作为 Spring 生态中负责安全的子项目,已经发展为一套功能完备、高度可定制的安全框架。理解它的核心架构,关键在于把握两个根本概念——认证(Authentication)授权(Authorization)——以及它们在框架内部是如何通过一条清晰的过滤器链串联起来的。

15.1.1 两大基石:认证与授权

认证回答的是“你是谁”的问题。系统需要确认当前访问者的身份,常见的方式包括用户名密码登录、手机验证码、OAuth2 社交登录、JWT 令牌等。认证成功后,系统会为当前会话或请求建立一个可信任的身份主体。

授权回答的是“你能做什么”的问题。即便用户已经登录,也不意味着可以访问所有资源。授权机制根据用户的角色、权限或自定义策略,判断是否允许执行某个操作或访问某个 URL。

在实际项目中,两者往往是分离的:认证模块负责建立身份,授权模块负责基于该身份进行访问控制。例如,一个后台管理系统可能先要求用户通过用户名密码登录(认证),然后根据其角色是“管理员”还是“普通编辑”决定是否允许删除文章(授权)。

15.1.2 安全上下文与 SecurityContextHolder

Spring Security 将认证成功后的用户信息存储在一个叫做 SecurityContext 的对象中,而该对象又被保存在 SecurityContextHolder 里。SecurityContextHolder 是整个安全框架的“全局变量”,应用程序的任何位置都可以通过它获取当前登录用户的信息:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();          // 获取用户名
boolean isAdmin = auth.getAuthorities().stream()
        .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));

SecurityContextHolder 默认使用 ThreadLocal 存储策略,确保同一个请求线程内各处获取的认证信息一致,处理完毕自动清除,避免内存泄漏。

15.1.3 认证流程的核心组件

当我们发起一个登录请求时,背后是一系列组件的协作。理解这些组件,是排查问题、自定义登录逻辑的基础。

1. Authentication(认证对象)

Authentication 是 Spring Security 中表示认证信息的核心接口。它在认证前和认证后承担不同角色:

  • 认证前:通常是一个包含用户名和密码的实例(如 UsernamePasswordAuthenticationToken),此时 isAuthenticated() 返回 false
  • 认证后:认证成功后,会返回一个全新的 Authentication 对象,其中包含用户的主体信息、权限集合,此时 isAuthenticated() 返回 true

2. AuthenticationManager

AuthenticationManager 是认证的入口接口,只定义了一个方法:

Authentication authenticate(Authentication authentication)

它会接收未认证的 Authentication 对象,执行认证逻辑,并返回已认证的 Authentication 对象。如果认证失败,则抛出相应的异常(如 BadCredentialsExceptionLockedException 等)。

在实际配置中,AuthenticationManager 通常是一个 ProviderManager,它内部维护着多个 AuthenticationProvider,依次尝试进行认证。最常见的 Provider 是 DaoAuthenticationProvider,它从数据库或其他数据源加载用户信息,并与输入的凭证比对。

3. UserDetailsService

UserDetailsService 是 Spring Security 与业务用户数据之间的桥梁。它只有一个方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

开发者需要实现这个接口,根据用户名从自己的用户表、缓存或其他存储中加载用户信息,并封装为框架可理解的 UserDetails 对象。UserDetails 包含了用户名、密码(已加密)、账户是否过期、是否锁定、以及授予的权限集合。

4. PasswordEncoder

明文存储密码是安全的大忌。Spring Security 强制要求使用 PasswordEncoder 对密码进行加密和验证。常用实现包括 BCryptPasswordEncoderSCryptPasswordEncoder 或者委托式的 DelegatingPasswordEncoder。配置一个全局的 PasswordEncoder Bean 后,DaoAuthenticationProvider 会在比对密码时自动调用 matches() 方法,开发者无需手写加密逻辑。

一个典型的认证流程可以这样描述

  1. 用户提交用户名密码,封装为 UsernamePasswordAuthenticationToken
  2. AuthenticationManager 调用 DaoAuthenticationProvider 处理该令牌。
  3. Provider 调用自定义的 UserDetailsService 加载用户信息。
  4. Provider 使用 PasswordEncoder 比对提交的密码与数据库中的哈希值。
  5. 比对成功,返回一个全新的、已认证的 Authentication 对象,并存入 SecurityContextHolder

15.1.4 授权流程:访问决策管理器

认证确立了身份,授权则决定了已认证的用户能否访问某个特定的资源。Spring Security 的授权核心是拦截器,具体表现为过滤器链中的 FilterSecurityInterceptor(或方法级安全拦截器)。它的决策过程依赖三个组件:

  • ConfigAttribute:安全配置中定义的访问规则,例如 hasRole('ADMIN')permitAll()
  • Authentication:当前请求的认证对象,包含用户的权限列表。
  • AccessDecisionManager:访问决策管理器,根据 ConfigAttribute 和 Authentication 进行投票决策。

默认的实现是 AffirmativeBased,即只要有一个投票器投赞成票,就允许访问;还有一种 ConsensusBased 多数决方式,以及 UnanimousBased 一票否决方式。投票器中最常用的是 RoleVoterWebExpressionVoter,前者检查角色前缀,后者支持 SpEL 表达式。

15.1.5 过滤器链:一切流量的入口

Spring Security 并非通过 Servlet 规范中的某一个过滤器实现安全,而是搭建了一条过滤器链。每个过滤器负责一项具体的安全任务,按顺序执行,共同构成了整个安全模型。核心过滤器包括(按顺序出现):

  • SecurityContextPersistenceFilter:请求到达时,从存储(如 HttpSession)中恢复 SecurityContext,请求结束时清理。
  • UsernamePasswordAuthenticationFilter:处理表单登录请求,执行认证,成功则重定向到目标页面。
  • BasicAuthenticationFilter:处理 HTTP Basic 方式认证,解析请求头中的 Authorization 信息。
  • ExceptionTranslationFilter:捕获过滤器链中抛出的 AuthenticationExceptionAccessDeniedException,分别引导到登录页或拒绝访问页面。
  • FilterSecurityInterceptor:链的最后一个安全过滤器,根据 URL 配置的访问规则,进行最终授权检查。

每一个 HTTP 请求都必须完整通过这条过滤器链。开发者在调试安全相关问题时,往往需要断开这条链中的某个环节,确认异常究竟是从哪里抛出的。

15.1.6 方法级安全与注解驱动

除了 URL 级别的控制,Spring Security 还支持方法级安全,通过 @EnableGlobalMethodSecurity 开启。常用注解包括:

  • @PreAuthorize:方法执行前校验,支持 SpEL 表达式,如 @PreAuthorize("hasRole('ADMIN') or #user.id == authentication.principal.id")
  • @PostAuthorize:方法执行后校验,可对返回结果进行过滤。
  • @Secured:早期的角色注解,只支持简单角色字符串。
  • @RolesAllowed:JSR-250 标准注解,同样用于角色控制。

方法级安全是通过 AOP 代理实现的,在方法调用前织入授权逻辑。如果在方法内部再次调用同一类的其他安全方法,代理不会生效,这是 Spring AOP 的固有限制,必要时需要重构方法调用或引入自注入。

15.1.7 实践中的架构最佳理解

在实际开发中,理解核心架构可以帮助我们快速定位问题、定制功能:

  • 认证失败:去检查 UserDetailsService 是否返回了正确的用户,PasswordEncoder 是否匹配,还是密码存储方式不一致。
  • 授权失败(403):检查 ConfigAttribute 配置是否正确,用户拥有的权限集合里是否真的包含所需角色。
  • 匿名用户访问了需要认证的资源:检查过滤器链是否把请求带到了 FilterSecurityInterceptor,是否未抛出认证异常就被判定权限不足。
  • 想扩展新的登录方式(如手机验证码):自定义 AuthenticationProvider,注册到 AuthenticationManager 中即可,不必改动过滤器链。

Spring Security 的架构虽然庞大,但核心只有一条:通过过滤器链识别用户(认证),基于配置好的规则判断用户是否能访问资源(授权)。抓牢这根主线,再复杂的应用安全需求都能被有条不紊地消化掉。接下来我们将通过实际配置,看看如何将这套架构落地为可运行的代码。