如何本地模拟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

