Azure返回加密Token及Spring Boot API自动验证与解密配置问询
问题解答
一、Azure 是否支持返回加密 Token?
Azure AD 支持返回加密的 ID Token 和 Access Token,需在应用注册中完成配置:
- 登录Azure门户,找到目标应用注册,进入「令牌配置」页面
- 启用令牌加密功能,上传用于加密的X.509证书公钥
- 配置完成后,Azure AD会用该公钥加密返回的Token,仅持有对应私钥的服务可解密
二、Spring Boot API 后端能否自动验证加密 Token?
你当前使用的 AadResourceServerHttpSecurityConfigurer 默认仅支持验证未加密的JWT Token,无法自动解密加密Token,需要手动添加解密逻辑。
三、如何在Security配置中实现Token解密?
你需要自定义JWT转换器,在Token被解析前完成解密,再交给Azure AD的验证逻辑处理。具体步骤如下:
1. 实现Token解密工具类
编写工具类,使用对应私钥解密Azure返回的加密Token:
@Component public class EncryptedTokenDecoder { private final PrivateKey privateKey; // 从配置路径加载私钥 public EncryptedTokenDecoder(@Value("${azure.token.encryption.private-key-path}") String privateKeyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { String privateKeyPem = Files.readString(Paths.get(privateKeyPath)); privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s", ""); byte[] keyBytes = Base64.getDecoder().decode(privateKeyPem); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); this.privateKey = keyFactory.generatePrivate(spec); } public String decryptToken(String encryptedToken) throws Exception { // 匹配Azure配置的加密算法,示例为RSA-OAEP Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decryptedBytes = cipher.doFinal(Base64.getUrlDecoder().decode(encryptedToken)); return new String(decryptedBytes, StandardCharsets.UTF_8); } }
2. 自定义JWT转换器
创建转换器,先解密Token,再交给默认Azure转换器处理:
@Component public class EncryptedAadJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> { private final AadJwtAuthenticationConverter defaultConverter; private final EncryptedTokenDecoder tokenDecoder; public EncryptedAadJwtAuthenticationConverter(EncryptedTokenDecoder tokenDecoder) { this.defaultConverter = new AadJwtAuthenticationConverter(); this.tokenDecoder = tokenDecoder; } @Override public AbstractAuthenticationToken convert(Jwt source) { try { // 解密原始Token得到未加密JWT字符串 String decryptedJwt = tokenDecoder.decryptToken(source.getTokenValue()); // 解析解密后的JWT为Jwt对象 JwtParser parser = Jwts.parser().verifyWith(/* 配置Azure公钥用于签名验证 */).build(); Jwt decryptedJwtObject = parser.parseSignedClaims(decryptedJwt).getPayload(); // 交给默认转换器处理 return defaultConverter.convert(decryptedJwtObject); } catch (Exception e) { throw new AuthenticationServiceException("Token解密或验证失败", e); } } }
3. 修改SecurityConfig配置
在你的SecurityFilterChain中,配置自定义转换器到AAD资源服务器:
@Configuration @EnableWebSecurity @EnableMethodSecurity @Slf4j public class SecurityConfig { private final EncryptedAadJwtAuthenticationConverter encryptedJwtConverter; // 注入自定义转换器 public SecurityConfig(EncryptedAadJwtAuthenticationConverter encryptedJwtConverter) { this.encryptedJwtConverter = encryptedJwtConverter; } public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors(cors -> cors.configurationSource(request -> { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("*")); configuration.setAllowedHeaders(Arrays.asList("*")); return configuration; })) .csrf().disable().httpBasic().disable().headers().xssProtection().and() .and().headers().frameOptions().sameOrigin() .and().authorizeHttpRequests().requestMatchers(HttpMethod.GET, "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/actuator/**").permitAll() .and().apply(AadResourceServerHttpSecurityConfigurer.aadResourceServer()) .jwt(jwt -> jwt.jwtAuthenticationConverter(encryptedJwtConverter)) // 配置自定义转换器 .and().authorizeHttpRequests().anyRequest().authenticated(); return http.build(); } }
注意事项
- BFF获取加密Token后需原封不动传递给API后端,不可修改Token内容
- 私钥需妥善保管,建议通过Azure Key Vault等密钥管理服务加载,避免硬编码
- 解密逻辑需与Azure AD配置的加密算法保持一致
内容的提问来源于stack exchange,提问作者robert trudel
相关产品推荐
相关产品推荐

