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

微服务架构下Spring Security会话验证权限拒绝异常咨询

嘿,这个问题我之前帮不少开发者踩过坑——网关层明明做了授权,下游服务还是抛出org.springframework.security.access.AccessDeniedException: Access is denied,核心问题大多是下游服务没正确识别网关传递的已认证会话/身份信息,或者自己额外触发了不必要的权限校验。下面给你一步步拆解解决方案,结合会话机制来搞定:

第一步:让网关正确传递会话/认证信息到下游

网关已经完成了认证授权,现在要把已认证主体的关键信息(用户ID、权限、认证状态)安全地传递给下游服务,这里有两种贴合会话机制的常用方式:

方式1:用Spring Session实现跨服务会话共享

如果网关和下游服务使用同一种会话存储(比如Redis),可以通过Spring Session让它们共享会话数据,这样下游服务能直接读取网关写入的认证信息:

  1. 网关+所有下游服务都引入Spring Session依赖(以Redis为例):
    <!-- Maven依赖 -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    
  2. 统一配置会话存储(application.yaml):
    spring:
      session:
        store-type: redis
      redis:
        host: your-redis-host
        port: your-redis-port
        password: your-redis-password # 如果有密码的话
    
  3. 网关认证通过后,Spring Security会自动把Authentication对象存入共享会话,下游服务只要配置Spring Security信任这个会话,就能直接获取已认证主体,无需重复认证。

方式2:网关将认证信息封装到请求头传递(轻量化方案)

如果不想共享会话,网关可以在认证通过后,把已认证用户的核心信息放到请求头里,下游服务从请求头提取并构建本地认证上下文:
网关侧自定义GatewayFilter实现:

@Component
public class AuthPropagationFilter implements GatewayFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 从网关的SecurityContext获取已认证主体
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            // 将用户ID、权限列表写入请求头
            exchange.getRequest().mutate()
                    .header("X-Auth-User-ID", auth.getName())
                    .header("X-Auth-Authorities", auth.getAuthorities().stream()
                            .map(GrantedAuthority::getAuthority)
                            .collect(Collectors.joining(",")))
                    .build();
        }
        return chain.filter(exchange);
    }
}
第二步:下游服务配置Spring Security信任网关的认证结果

下游服务默认会自己触发认证校验逻辑,所以必须修改配置,让它信任网关传递的认证信息,避免重复拦截:

针对会话共享场景的配置

下游服务的Security配置类要关闭不必要的认证过滤器,同时允许从共享会话读取认证信息:

@Configuration
@EnableWebSecurity
public class DownstreamSecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                // 可以限制仅允许网关IP的请求,更安全
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                // 复用共享会话,不需要下游自己创建新会话
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            )
            // 关闭网关已经做过的认证方式(比如表单登录、Basic认证)
            .formLogin().disable()
            .httpBasic().disable();
        return http.build();
    }
}

针对请求头传递场景的配置

下游服务需要自定义过滤器,从请求头提取信息并构建Authentication对象,存入本地SecurityContext:

@Component
public class AuthContextInitializerFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String userId = request.getHeader("X-Auth-User-ID");
        String authoritiesStr = request.getHeader("X-Auth-Authorities");
        
        if (userId != null && authoritiesStr != null) {
            // 从请求头解析权限列表
            List<GrantedAuthority> authorities = Arrays.stream(authoritiesStr.split(","))
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
            // 构建已认证的Authentication对象
            Authentication auth = new UsernamePasswordAuthenticationToken(userId, null, authorities);
            // 存入SecurityContext,让后续的权限校验能识别
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);
    }
}

然后在Security配置中注册这个过滤器,放到Spring Security的默认认证过滤器之前:

@Configuration
@EnableWebSecurity
public class DownstreamSecurityConfig {
    @Autowired
    private AuthContextInitializerFilter authContextFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(authContextFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            // 下游服务可以设置为无状态,因为认证信息来自请求头
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .formLogin().disable()
            .httpBasic().disable();
        return http.build();
    }
}
第三步:排查常见坑点
  • 确保网关和下游服务的Spring Security、Spring Session版本一致,避免会话序列化/反序列化失败
  • 如果用Redis共享会话,检查序列化配置,推荐用Jackson2JsonRedisSerializer来序列化Authentication对象,避免默认JDK序列化的兼容性问题
  • 下游服务如果用了@PreAuthorize等细粒度权限注解,要确保权限字符串和网关传递的完全一致(比如网关传的是ROLE_ADMIN,注解里就不能写ADMIN)
  • 多线程场景下(比如下游服务用异步方法),要设置SecurityContextHolder的策略为MODE_INHERITABLETHREADLOCAL,避免线程切换后丢失认证信息:
    @PostConstruct
    public void initSecurityContextStrategy() {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:43:31