Spring 在 Azure 中启动 - 请求中的客户端证书 Header
Spring Boot in Azure - Client Certificate in Request Header
我们目前在 Spring 启动应用程序中实现了相互身份验证,需要将其部署在 Azure 中。
Azure 的负载均衡器在请求 header 字段 "X-ARR-ClientCert" 中重定向客户端证书(Base64 编码),但 Spring 无法在那里找到它。
=> 身份验证失败
Microsoft 文档显示了如何在 .NET 应用程序中处理此问题:https://docs.microsoft.com/en-gb/azure/app-service-web/app-service-web-configure-tls-mutual-auth
我尝试从 OncePerRequestFilter 中的 header 中提取证书并将其设置为这样的请求:
public class AzureCertificateFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(AzureCertifacteFilter.class);
private static final String AZURE_CLIENT_CERTIFICATE_HEADER = "X-ARR-ClientCert";
private static final String JAVAX_SERVLET_REQUEST_X509_CERTIFICATE = "javax.servlet.request.X509Certificate";
private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n";
private static final String END_CERT = "\n-----END CERTIFICATE-----";
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
X509Certificate x509Certificate = extractClientCertificate(httpServletRequest);
// azure redirects the certificate in a header field
if (x509Certificate == null && StringUtils.isNotBlank(httpServletRequest.getHeader(AZURE_CLIENT_CERTIFICATE_HEADER))) {
String x509CertHeader = BEGIN_CERT + httpServletRequest.getHeader(AZURE_CLIENT_CERTIFICATE_HEADER) + END_CERT;
try (ByteArrayInputStream certificateStream = new ByteArrayInputStream(x509CertHeader.getBytes())) {
X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificateStream);
httpServletRequest.setAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE, certificate);
} catch (CertificateException e) {
LOG.error("X.509 certificate could not be created out of the header field {}. Exception: {}", AZURE_CLIENT_CERTIFICATE_HEADER, e.getMessage());
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private X509Certificate extractClientCertificate(HttpServletRequest request) {
X509Certificate[] certs = (X509Certificate[]) request.getAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE);
if (certs != null && certs.length > 0) {
LOG.debug("X.509 client authentication certificate:" + certs[0]);
return certs[0];
}
LOG.debug("No client certificate found in request.");
return null;
}
}
但是稍后在 Spring 过滤器链中失败,出现以下异常:
sun.security.x509.X509CertImpl cannot be cast to [Ljava.security.cert.X509Certificate; /oaa/v1/spaces
配置如下所示:
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("**/docs/restapi/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.disable()
.addFilterBefore(new AzureCertificateFilter(), X509AuthenticationFilter.class)
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService());
}
我应该更仔细地阅读异常:
sun.security.x509.X509CertImpl cannot be cast to [Ljava.security.cert.X509Certificate; /oaa/v1/spaces
我必须像这样设置一组证书:
httpServletRequest.setAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE, new X509Certificate[]{x509Certificate});
我们目前在 Spring 启动应用程序中实现了相互身份验证,需要将其部署在 Azure 中。 Azure 的负载均衡器在请求 header 字段 "X-ARR-ClientCert" 中重定向客户端证书(Base64 编码),但 Spring 无法在那里找到它。 => 身份验证失败
Microsoft 文档显示了如何在 .NET 应用程序中处理此问题:https://docs.microsoft.com/en-gb/azure/app-service-web/app-service-web-configure-tls-mutual-auth
我尝试从 OncePerRequestFilter 中的 header 中提取证书并将其设置为这样的请求:
public class AzureCertificateFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(AzureCertifacteFilter.class);
private static final String AZURE_CLIENT_CERTIFICATE_HEADER = "X-ARR-ClientCert";
private static final String JAVAX_SERVLET_REQUEST_X509_CERTIFICATE = "javax.servlet.request.X509Certificate";
private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n";
private static final String END_CERT = "\n-----END CERTIFICATE-----";
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
X509Certificate x509Certificate = extractClientCertificate(httpServletRequest);
// azure redirects the certificate in a header field
if (x509Certificate == null && StringUtils.isNotBlank(httpServletRequest.getHeader(AZURE_CLIENT_CERTIFICATE_HEADER))) {
String x509CertHeader = BEGIN_CERT + httpServletRequest.getHeader(AZURE_CLIENT_CERTIFICATE_HEADER) + END_CERT;
try (ByteArrayInputStream certificateStream = new ByteArrayInputStream(x509CertHeader.getBytes())) {
X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificateStream);
httpServletRequest.setAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE, certificate);
} catch (CertificateException e) {
LOG.error("X.509 certificate could not be created out of the header field {}. Exception: {}", AZURE_CLIENT_CERTIFICATE_HEADER, e.getMessage());
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private X509Certificate extractClientCertificate(HttpServletRequest request) {
X509Certificate[] certs = (X509Certificate[]) request.getAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE);
if (certs != null && certs.length > 0) {
LOG.debug("X.509 client authentication certificate:" + certs[0]);
return certs[0];
}
LOG.debug("No client certificate found in request.");
return null;
}
}
但是稍后在 Spring 过滤器链中失败,出现以下异常:
sun.security.x509.X509CertImpl cannot be cast to [Ljava.security.cert.X509Certificate; /oaa/v1/spaces
配置如下所示:
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("**/docs/restapi/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.disable()
.addFilterBefore(new AzureCertificateFilter(), X509AuthenticationFilter.class)
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService());
}
我应该更仔细地阅读异常:
sun.security.x509.X509CertImpl cannot be cast to [Ljava.security.cert.X509Certificate; /oaa/v1/spaces
我必须像这样设置一组证书:
httpServletRequest.setAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE, new X509Certificate[]{x509Certificate});