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

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

## 環境
- spring-boot 3.0.6
- spring-security 6.0.3

[SpringSecurityの仕様](https://spring.pleiades.io/spring-security/reference/features/exploits/headers.html#headers-default)を見るとStrict-Transport-Securityはデフォルトで付与されるようです。

```kotlin: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 リクエストにのみ追加されます`

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

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

```yaml:application.yaml
server:
  tomcat:
    remoteip:
      protocol-header: x-forwarded-proto
```
この設定により`Strict-Transport-Security`が付与されるようになりました。


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

```java: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の結果を返しています。

```java: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となるよう[実装](https://github.com/apache/tomcat/blob/897931b68c89788eeb71398c8e6dfda1d5bae161/java/org/apache/catalina/valves/RemoteIpValve.java#LL680C45-L680C45)されていました。

```java: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:）