You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring AOP切面中正确获取Spring Security用户认证的方案

Spring AOP切面中安全获取Spring Security用户认证的方案

你的担心在常规同步请求场景下其实是多余的,下面结合Spring Security的机制和实践方案给你说明:

1. 为什么并发场景下默认不会出现上下文混乱

Spring Security默认使用THREADLOCAL作为SecurityContextHolder的存储策略,每个请求线程都会持有独立的SecurityContext实例,并发请求之间的上下文完全隔离,不会出现互相覆盖的情况。只要你没有手动修改过存储策略(比如改成全局共享模式),同步接口的并发调用就不会有问题。

2. 优化用户信息获取的安全性

你当前的fetchUserId方法可以优化类型转换逻辑,避免出现ClassCastException:

private Long fetchUserId() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    // 先判断类型再强转,避免类型不匹配报错
    if (authentication instanceof JwtAuthentication jwtAuth) {
        return jwtAuth.getId();
    }
    // 根据业务需求处理未认证场景:返回null/抛出异常/返回默认值
    return null;
}

3. 异步场景的特殊处理

如果你的接口使用@Async异步执行,或者审计逻辑涉及异步操作,默认的THREADLOCAL策略会导致子线程无法继承父线程的Security上下文,这时候可以:

  • 修改存储策略为MODE_INHERITABLETHREADLOCAL,让子线程自动继承父线程上下文:
    @Component
    public class SecurityContextConfig {
        @PostConstruct
        public void setupSecurityContextStrategy() {
            SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
        }
    }
    
  • 或者在异步任务执行前手动传递上下文(适合局部异步场景):
    // 主线程提前获取上下文
    SecurityContext context = SecurityContextHolder.getContext();
    // 异步执行时手动设置上下文
    CompletableFuture.runAsync(() -> {
        SecurityContextHolder.setContext(context);
        // 执行审计逻辑
    });
    

4. 切面代码的简洁性优化

可以把重复的审计逻辑抽成通用切面,用@Around注解统一处理用户信息获取和日志记录,避免每个切点方法重复写相同代码:

@Around("execution(* com.sdx.rootservice.controller.company.AppealTypeController.create(..)) || execution(* com.sdx.rootservice.controller.company.AppealTypeController.delete(..))")
public Object aroundControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
    // 提前获取用户信息
    Long userId = fetchUserId();
    // 执行目标接口方法
    Object result = joinPoint.proceed();
    // 根据方法类型记录审计日志
    String methodName = joinPoint.getSignature().getName();
    if ("create".equals(methodName)) {
        AppealTypeDto appealTypeDto = (AppealTypeDto) result;
        auditLogService.logChanges(userId, "AppealType", appealTypeDto.getId(), AuditType.CREATE);
    } else if ("delete".equals(methodName)) {
        Long id = (Long) joinPoint.getArgs()[0];
        auditLogService.logChanges(userId, "AppealType", id, AuditType.DELETE);
    }
    return result;
}

内容的提问来源于stack exchange,提问作者Maksym Gaiduchek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:50:11