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

如何本地模拟Google IAP认证以测试Spring Boot应用

问题描述

我有一个部署在Cloud Run上、采用Spring Boot开发并启用IAP认证的应用,本地测试时出现如下错误:

java.lang.NullPointerException: Cannot invoke "org.springframework.security.core.Authentication.getName()" because "authentication" is null
at com.wind.cbrweb.web.HomeController.handleRequest(HomeController.java:56) ~[classes/:0.0.1-SNAPSHOT]

使用的依赖:

<dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-security-iap</artifactId>
</dependency>

安全配置类:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
    
    protected final Log logger = LogFactory.getLog(getClass());  
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authorizeHttpRequests) ->
                    authorizeHttpRequests
                    .requestMatchers("/**").permitAll().anyRequest().authenticated())
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        
       http.headers((headers) -> headers
           .frameOptions((frameOptions) -> frameOptions.disable()));

        return http.build();
    }
    
}

application.properties配置:

spring.cloud.gcp.security.iap.registry=https://www.gstatic.com/iap/verify/public_key-jwk
spring.cloud.gcp.security.iap.algorithm=ES256
spring.cloud.gcp.security.iap.header=x-goog-iap-jwt-assertion
spring.cloud.gcp.security.iap.issuer=https://cloud.google.com/iap
spring.cloud.gcp.security.iap.audience=/projects/PROJECT_ID/global/backendServices/SERVICE_ID

首页控制器代码:

@Controller
public class HomeController {
    protected final Log logger = LogFactory.getLog(getClass());
    
    @Autowired
    private UserManager userManager;
    
    @Autowired
    private Firestore firestore;
    
    @GetMapping("/")
    public String redirectToHome(Authentication authentication) {
        return "redirect:/home.htm";
    }

    @GetMapping("/home.htm")
    public ModelAndView handleRequest(HttpServletRequest req,
            HttpServletResponse res, Authentication authentication) throws Exception {
        logger.info("Returning home view");
        
        String currentUserName = authentication.getName();
        
        //OTHER CODE
    }
}

需求:如何创建模拟用户来模拟Google IAP认证?期望本地运行Spring应用访问/home时,Authentication能填充用户主体信息(所属组、姓名、user_id等),实现与IAP一致的认证模拟。


解决方案

要在本地模拟IAP认证,核心是在开发环境中注入符合IAP格式的认证信息,同时不干扰生产环境配置,以下两种方式可选:

方式一:快速模拟认证(适合手动测试)

新增仅在dev环境生效的安全配置,直接注入模拟用户信息:

@Configuration
@Profile("dev") // 仅开发环境启用
@EnableWebSecurity
public class DevSecurityConfiguration {

    @Bean
    public SecurityFilterChain devFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults())
            .userDetailsService(mockUserDetailsService());
        return http.build();
    }

    @Bean
    public UserDetailsService mockUserDetailsService() {
        // 模拟IAP返回的用户属性,包含user_id、姓名、组信息
        return username -> {
            UserDetails baseUser = User.withUsername("test-user@example.com")
                .password("{noop}test123") // noop表示不加密,仅本地测试用
                .roles("USER", "ADMIN")
                .build();

            // 封装IAP特有的扩展属性
            return new User(baseUser.getUsername(), baseUser.getPassword(), baseUser.getAuthorities()) {
                @Override
                public Map<String, Object> getAttributes() {
                    Map<String, Object> attrs = new HashMap<>();
                    attrs.put("sub", "1234567890"); // 对应IAP的user_id
                    attrs.put("name", "测试用户");
                    attrs.put("email", "test-user@example.com");
                    attrs.put("groups", Arrays.asList("team-a@example.com", "admin-group@example.com"));
                    return attrs;
                }
            };
        };
    }
}

在application-dev.properties中激活开发环境:

spring.profiles.active=dev

启动应用后,访问页面会跳转到Spring Security默认登录页,使用test-user@example.com和test123登录,此时Authentication对象会携带模拟的用户信息。

方式二:模拟IAP JWT流程(贴近生产环境)

完全模拟IAP的JWT认证逻辑,生成符合格式的自签名JWT并注入请求头:

步骤1:生成模拟IAP格式的JWT

添加com.auth0:java-jwt依赖(仅测试用),编写生成工具:

// 本地测试用JWT生成工具
public class MockIapJwtGenerator {
    public static String generateMockJwt() {
        Algorithm algorithm = Algorithm.HMAC256("local-test-secret");
        return JWT.create()
            .withIssuer("https://cloud.google.com/iap") // 匹配配置中的issuer
            .withAudience("/projects/PROJECT_ID/global/backendServices/SERVICE_ID") // 匹配配置的audience
            .withSubject("1234567890") // user_id
            .withClaim("name", "测试用户")
            .withClaim("email", "test-user@example.com")
            .withClaim("groups", Arrays.asList("team-a@example.com", "admin-group@example.com"))
            .withExpiresAt(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期
            .sign(algorithm);
    }
}

步骤2:修改开发环境的JWT验证逻辑

@Configuration
@Profile("dev")
@EnableWebSecurity
public class DevIapSecurityConfiguration {

    @Bean
    public SecurityFilterChain devFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.decoder(mockJwtDecoder())));
        return http.build();
    }

    @Bean
    public JwtDecoder mockJwtDecoder() {
        // 本地使用自签名密钥验证JWT
        SecretKey secretKey = Keys.hmacShaKeyFor("local-test-secret".getBytes(StandardCharsets.UTF_8));
        return NimbusJwtDecoder.withSecretKey(secretKey).build();
    }
}

步骤3:自动注入模拟JWT请求头

添加过滤器,自动为本地请求带上IAP认证头:

@Component
@Profile("dev")
public class MockIapHeaderFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String mockJwt = MockIapJwtGenerator.generateMockJwt();
        HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) {
            @Override
            public String getHeader(String name) {
                if ("x-goog-iap-jwt-assertion".equals(name)) {
                    return mockJwt;
                }
                return super.getHeader(name);
            }
        };
        filterChain.doFilter(wrappedRequest, response);
    }
}

启动应用后,所有请求会自动携带模拟的IAP JWT,Authentication对象会和生产环境一样填充用户信息。

注意事项

  • 开发环境配置文件需单独存放,避免打包到生产环境
  • 模拟的用户属性要和IAP实际返回字段一致(比如sub对应user_id)
  • 方式一适合快速手动测试,方式二更适合集成测试或还原生产认证流程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 00:35:02