1. 概述

Spring Security 帮我们处理了认证信息的接收与解析,开发者无需从头实现。

本文将聚焦一个实用功能:如何在处理请求的代码中,便捷地获取当前的 SecurityContext 信息。你会看到,相比传统方式,新注解能让代码更简洁、更清晰。

2. @CurrentSecurityContext 注解简介

过去,获取认证信息通常需要写这样的样板代码:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();

虽然可行,但侵入性强,且每次都要重复。✅ 现在有了更优雅的方案:@CurrentSecurityContext 注解。

这个注解的核心价值在于:

  • 声明式编程:直接在方法参数上声明依赖,无需手动调用静态工具类
  • 依赖注入AuthenticationPrincipal 对象可被自动注入
  • 灵活取值:结合 SpEL(Spring 表达式语言),可精确提取上下文中的任意属性

接下来,我们将通过实际例子展示如何用它获取 AuthenticationPrincipal,并附上测试验证。

3. Maven 依赖配置

如果你使用的是较新版本的 Spring Boot,只需引入标准安全模块:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

若非 Spring Boot 项目,则直接引入 spring-security-core

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>6.1.5</version>
</dependency>

⚠️ 注意:@CurrentSecurityContext 在 Spring Security 5.2+ 版本中可用,建议保持版本一致性。

4. 使用 @CurrentSecurityContext 实现上下文注入

@CurrentSecurityContext 支持通过 SpEL 表达式指定要注入的对象类型。SpEL 会结合参数类型进行解析,类型安全可通过 errorOnInvalidType 参数开启校验。

4.1 获取 Authentication 对象

最常见需求之一是获取完整的 Authentication 对象,比如查看认证详情(details):

@GetMapping("/authentication")
public Object getAuthentication(@CurrentSecurityContext(expression = "authentication") 
  Authentication authentication) {
    return authentication.getDetails();
}

📌 关键点:

  • expression = "authentication" 表示注入 SecurityContext.authentication 本身
  • 参数类型为 Authentication,Spring 会自动完成类型匹配

测试示例

@Test
public void givenOAuth2Context_whenAccessingAuthentication_ThenRespondTokenDetails() {
    ClientCredentialsResourceDetails resourceDetails = 
      getClientCredentialsResourceDetails("user123", java.util.Collections.singletonList("read"));
    OAuth2RestTemplate restTemplate = getOAuth2RestTemplate(resourceDetails);

    String authentication = executeGetRequest(restTemplate, "/authentication");

    // 因 remoteAddress 和 tokenValue 动态生成,使用正则匹配结构
    Pattern pattern = Pattern.compile("\\{\"remoteAddress\":\".*"
      + "\",\"sessionId\":null,\"tokenValue\":\".*"
      + "\",\"tokenType\":\"Bearer\",\"decodedDetails\":null}");
    assertTrue("authentication 结构应匹配", pattern.matcher(authentication).matches());
}

✅ 踩坑提示:测试中不要断言具体 token 值,因其每次请求都不同。建议用正则校验 JSON 结构即可。

4.2 获取 Principal 对象

如果只需要用户身份标识(如用户名),可以直接注入 Principal

@GetMapping("/principal")
public String getPrincipal(@CurrentSecurityContext(expression = "authentication.principal") 
  Principal principal) { 
    return principal.getName(); 
}

📌 关键点:

  • SpEL 表达式为 authentication.principal,指向认证主体
  • 参数类型为 Principal,框架自动完成转换

测试示例

@Test
public void givenOAuth2Context_whenAccessingPrincipal_ThenRespondUser123() {
    ClientCredentialsResourceDetails resourceDetails = 
       getClientCredentialsResourceDetails("user123", java.util.Collections.singletonList("read"));
    OAuth2RestTemplate restTemplate = getOAuth2RestTemplate(resourceDetails);

    String principal = executeGetRequest(restTemplate, "/principal");

    assertEquals("user123", principal);
}

✅ 简单粗暴:测试中我们明确知道 client credentials 中设置的用户名是 user123,因此可以直接断言返回值。

5. 总结

@CurrentSecurityContext 是一个被低估但极其实用的注解,它让安全上下文的访问变得:

  • 更简洁:告别 SecurityContextHolder.getContext() 的冗长调用
  • 更安全:支持类型检查,减少运行时错误
  • 更灵活:通过 SpEL 可定制任意属性提取逻辑

在实际项目中,尤其在 REST 接口或自定义鉴权逻辑中,推荐优先使用此注解替代手动获取上下文的方式。

源码示例已托管至 GitHub:https://github.com/example/spring-security-current-context-demo


原始标题:Guide to @CurrentSecurityContext in Spring Security