LoginSignup
4
2

More than 1 year has passed since last update.

AWS ALB配下のspringbootでStrict-Transport-Securityヘッダーが付与されない

Posted at

発生した問題

ALBにECMのSSL証明書を付けてSSL終端している環境で、spring-bootのアプリケーションにhttpsでアクセスしてもStrict-Transport-Securityヘッダーが付与されませんでした。

環境

  • spring-boot 3.0.6
  • spring-security 6.0.3

SpringSecurityの仕様を見るとStrict-Transport-Securityはデフォルトで付与されるようです。

defaultで付与されるヘッダー
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 0

但し、メモとして以下の記載があります。
Strict-Transport-Security は HTTPS リクエストにのみ追加されます

今回の事象はhttpsでアクセスしてもStrict-Transport-Securityが付与されないというものです。

解決方法

結論、ALBでSSL終端する際にはapplication.yamlでtomcatがx-forwarded-protoを参照する様に設定する事でSSLでアクセスされていると認識してくれます。

application.yaml
server:
  tomcat:
    remoteip:
      protocol-header: x-forwarded-proto

この設定によりStrict-Transport-Securityが付与されるようになりました。

補足

SpringSecurityがどの様にStrict-Transport-Securityの付与を判断しているのか気になったので調べました。
HstsHeaderWriterクラスのwriteHeadersがHttpServletResponseのsetHeaderで付与しています。

org.springframework.security.web.header.writers.HstsHeaderWriter.java
@Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
  if (!this.requestMatcher.matches(request)) { // matchesがfalseならヘッダー付与せずにreturn
    if (this.logger.isTraceEnabled()) {
      this.logger.trace(LogMessage.format("Not injecting HSTS header since it did not match request to [%s]",
          this.requestMatcher));
    }
    return;
  }
  if (!response.containsHeader(HSTS_HEADER_NAME)) {
    response.setHeader(HSTS_HEADER_NAME, this.hstsHeaderValue); // ここで付与している
  }
}

matchesはHstsHeaderWriterの内部クラスのSecureRequestMatcherでHttpServletRequestのisSecureの結果を返しています。

SecureRequestMatcher
private static final class SecureRequestMatcher implements RequestMatcher {
  @Override
  public boolean matches(HttpServletRequest request) {
    return request.isSecure(); // 判定はここでしている
  }
}

どの様にHttpServletRequestのSecureが設定されるのかを確認するためにTomcatのソースも見てみました。
TomcatではRemoteIpValveクラスでX-Forwarded-Protoヘッダーがあるか判定し、値がhttpsであればSecureがtrueとなるよう実装されていました。

RemoteIpValve(抜粋)
private String protocolHeader = "X-Forwarded-Proto";

// 判定処理
if (protocolHeader != null) {
  // 1. X-Forwarded-Protoヘッダーを取得
  String protocolHeaderValue = request.getHeader(protocolHeader); 
  if (protocolHeaderValue == null) {
  // 2. ヘッダーの値がnullでなければisForwardedProtoHeaderValueSecureメソッドに渡す
  // メソッド内部ではprotocolHeaderValueの値がhttpsかを判定している
  } else if (isForwardedProtoHeaderValueSecure(protocolHeaderValue)) { 
    // 3. isForwardedProtoHeaderValueSecureがtrueならSecureがtrueになる
    request.setSecure(true);
    request.getCoyoteRequest().scheme().setString("https");
    setPorts(request, httpsServerPort);
  } else {
    request.setSecure(false);
    request.getCoyoteRequest().scheme().setString("http");
    setPorts(request, httpServerPort);
  }
}

これで、spring-bootのアプリが無事isSecure=trueだと認識して動作するんですね。

ALBにACMの証明書を付けてSSL終端し、ターゲットグループのport 80に流すのはよくある構成だと思います。同じようにはまる人の助けになればと思います。
(正直自分が良く忘れるので自分のためなんですが:frowning2:

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2