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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 12:32:35