人人都会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 对象。如果认证失败,则抛出相应的异常(如 BadCredentialsException、LockedException 等)。

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

3. UserDetailsService

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

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

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

4. PasswordEncoder

明文存储密码是安全的大忌。Spring Security 强制要求使用 PasswordEncoder 对密码进行加密和验证。常用实现包括 BCryptPasswordEncoder、SCryptPasswordEncoder 或者委托式的 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 一票否决方式。投票器中最常用的是 RoleVoter 和 WebExpressionVoter,前者检查角色前缀,后者支持 SpEL 表达式。

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

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

  • SecurityContextPersistenceFilter:请求到达时,从存储(如 HttpSession)中恢复 SecurityContext,请求结束时清理。
  • UsernamePasswordAuthenticationFilter:处理表单登录请求,执行认证,成功则重定向到目标页面。
  • BasicAuthenticationFilter:处理 HTTP Basic 方式认证,解析请求头中的 Authorization 信息。
  • ExceptionTranslationFilter:捕获过滤器链中抛出的 AuthenticationException 和 AccessDeniedException,分别引导到登录页或拒绝访问页面。
  • 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 的架构虽然庞大,但核心只有一条:通过过滤器链识别用户(认证),基于配置好的规则判断用户是否能访问资源(授权)。抓牢这根主线,再复杂的应用安全需求都能被有条不紊地消化掉。接下来我们将通过实际配置,看看如何将这套架构落地为可运行的代码。