Spring Security:仅在授权访问接口时触发登录成功操作
解决Spring Security中仅在权限验证通过后执行操作的问题
你遇到的问题本质是认证(Authentication)和授权(Authorization)是Spring Security中两个完全独立的阶段:AuthenticationSuccessEvent只负责监听「用户登录成功」这个认证节点事件,不管后续用户有没有访问目标接口的权限,只要用户名密码校验通过就会触发。而你需要的是在**授权校验成功(用户拥有对应接口的访问权限)**时执行指定操作,所以得从授权阶段的钩子入手。
下面结合你的代码场景,给你几个可行的实现方案:
方案一:监听Spring Security的AuthorizationSuccessEvent(推荐,Spring Security 5.6+)
从Spring Security 5.6版本开始,框架原生提供了AuthorizationSuccessEvent,这个事件会在所有授权校验通过后触发,完美匹配你的需求。
实现代码:
@Component public class AuthorizationSuccessEventListener implements ApplicationListener<AuthorizationSuccessEvent> { private static final Logger log = LoggerFactory.getLogger(AuthorizationSuccessEventListener.class); @Autowired private HttpServletRequest request; @Override public void onApplicationEvent(AuthorizationSuccessEvent event) { // 获取当前登录用户信息 Authentication authentication = event.getAuthentication(); String username = ((UserAccountDetails) authentication.getPrincipal()).getUsername(); // 获取当前请求的接口和HTTP方法信息 String requestUri = request.getRequestURI(); String httpMethod = request.getMethod(); log.info("用户 {} 成功通过权限校验,访问接口:{} {}", username, httpMethod, requestUri); // 在这里执行你需要的记录/自定义操作逻辑 } }
注意:
如果你的Spring Security版本低于5.6,建议升级到对应稳定版本,享受这个原生事件带来的便捷性。
方案二:自定义AOP切面拦截带@PreAuthorize的方法
如果你无法升级框架版本,可以用Spring AOP来切面所有标记了@PreAuthorize的方法——因为@PreAuthorize是在方法执行前做权限校验,只要方法能正常执行,就说明权限校验已经通过。
实现代码:
- 切面类实现:
@Component @Aspect public class PreAuthorizeSuccessAspect { private static final Logger log = LoggerFactory.getLogger(PreAuthorizeSuccessAspect.class); @Autowired private HttpServletRequest request; // 拦截所有带有@PreAuthorize注解的方法,在方法执行后触发逻辑 @AfterReturning("@annotation(org.springframework.security.access.prepost.PreAuthorize)") public void afterPreAuthorizeSuccess(JoinPoint joinPoint) { // 获取当前登录用户 UserAccountDetails user = (UserAccountDetails) AuthorizationUtils.getUserDetails(); if (user == null) { return; } String username = user.getUsername(); // 获取方法和接口信息 String methodName = joinPoint.getSignature().getName(); String requestUri = request.getRequestURI(); String httpMethod = request.getMethod(); log.info("用户 {} 成功通过权限校验,执行方法:{},访问接口:{} {}", username, methodName, httpMethod, requestUri); // 执行你的记录/自定义操作逻辑 } }
- 开启AOP支持:
在你的Spring配置类上添加@EnableAspectJAutoProxy注解,确保AOP切面生效。
方案三:扩展MethodSecurityInterceptor(底层定制)
如果你需要更底层的权限控制扩展,可以继承Spring Security处理方法级权限的核心类MethodSecurityInterceptor,在授权成功后插入自定义逻辑。
实现代码:
- 自定义Interceptor:
@Component public class CustomMethodSecurityInterceptor extends MethodSecurityInterceptor { private static final Logger log = LoggerFactory.getLogger(CustomMethodSecurityInterceptor.class); @Autowired private HttpServletRequest request; @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 先执行Spring Security原生的授权逻辑 Object result = super.invoke(invocation); // 能走到这里说明权限校验已通过 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String username = ((UserAccountDetails) authentication.getPrincipal()).getUsername(); String requestUri = request.getRequestURI(); String httpMethod = request.getMethod(); log.info("用户 {} 成功通过权限校验,访问接口:{} {}", username, httpMethod, requestUri); // 执行你的记录/自定义操作逻辑 return result; } }
- 配置Spring Security使用自定义Interceptor:
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private CustomMethodSecurityInterceptor customMethodSecurityInterceptor; @Override protected MethodSecurityInterceptor methodSecurityInterceptor() { return customMethodSecurityInterceptor; } }
额外提示:
- 方案一的
AuthorizationSuccessEvent会覆盖所有授权场景(包括URL级权限校验、方法级权限校验),如果只想针对@PreAuthorize的方法,可以在监听器里判断事件的AuthorizationDecision来源。 - 所有方案中获取用户信息时建议做判空处理,避免极端情况下的空指针问题(不过你的接口都加了权限校验,正常场景下用户都是已登录状态)。
内容的提问来源于stack exchange,提问作者Vjekoslav Krainovic
相关产品推荐
相关产品推荐

