ALBのOIDC認証をSpring Securityで使用してみた。


***認証が辛い！！***　もっと楽に安全に認証したい。したいですよね？  
そういえば、ALBがOIDC認証に対応していたな、Spring Securityに食わせれば手間いらずで認証できるんじゃ・・・？


## 事前準備
* ALBのhttps設定は以下を参考に設定  
  https://qiita.com/Yuki_BB3/items/fd410ef29935169aad9d
* Amazon CognitoやGoogle Identity PlatformでOIDCの設定をする

## ALBの仕様
検証が成功してターゲットグループ（Spring Securityが載ってるアプリ）を呼ぶときにはヘッダーの```x-amzn-oidc-data```にJWTを乗せて送ってくる。

* 2回目以降もセッションが有効であれば毎回ちゃんと乗せてくれる。
* 有効期限(exp)は3分ぐらいとかなり短い(厳密には計ってない)
* 署名はECDSA + P-256 + SHA256

## Spring Securityの設定
まず、主となるのが以下のフィルターこのフィルターが今回すべての起点となる。
```org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter```

見ての通り本来はbearerトークン用ではあるもののトークンはJWTが入ってくることが決め打ちになってるので、今回はこれを活用する。
その際、JWTの検証や詰め替えは次のアーティファクトが担っている。
```org.springframework.security:spring-security-oauth2-jose```

### bearerトークンではなく```x-amzn-oidc-data```をトークンとして認識させる
以下を継承したクラスを作成
```org.springframework.security.oauth2.server.resource.web.BearerTokenResolver```

```java:ALBTokenResolver
public class ALBTokenResolver implements BearerTokenResolver {
	@Override
	public String resolve(HttpServletRequest request) {
		return request.getHeader("x-amzn-oidc-data");
	}
}
```
このような形で任意のヘッダーやリクエストボディもトークンとして認識させることができる。

### JWTの検証
``` org.springframework.security.oauth2.jwt.JwtDecoder ```がJWTのパースから検証までを行っている。
実体は``` org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport ```である。

#### 公開鍵の取得
まず、JWTを署名した公開鍵は所定のURLからPEMでEC公開鍵が得られるので、それに合わせて```com.nimbusds.jose.proc.JWSVerificationKeySelector```を継承したクラスを作成する。
実装は```com.nimbusds.jose.util.X509CertUtils```を参考に作成する。
長いので以下のリンクを参照
https://github.com/tac-yacht/Sample_SpringSecurityForAWSALB/blob/master/src/main/java/com/example/auth/ALBPublicKeySelector.java
※JwtDecoderから来るjwkSetUrlを流用しているため、application.ymlに以下を設定すること（決め打ちとする場合はこの限りでは無い）

```yml:application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://public-keys.auth.elb.us-west-2.amazonaws.com/
```

#### JwtDecoderへの取得処理の設定
実体は前述したとおり```NimbusJwtDecoderJwkSupport ```なのだが、任意の署名アルゴリズムと鍵取得が設定できない。
そのためまるごとコピーして以下の部分に先ほどのKeySelectorで初期化する。

```java:private_constractor(String,String)
		JWSKeySelector<SecurityContext> jwsKeySelector =
			new ALBPublicKeySelector<SecurityContext>(this.jwsAlgorithm, jwkSource); //ここ
```

アルゴリズムは以下の部分を書き換える

```java:public_constractor
	public ALBJwtDecoder(@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUrl) {
		this(jwkSetUrl, JwsAlgorithms.ES256);
}
```

### 処理利用の設定
あとはほかのSpring Security同様ConfigJavaを記述

```java:OAuth2ResourceServerSecurityConfiguration.java
@EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
	@Autowired
	private ALBTokenResolver resolver;
	@Autowired
	private ALBJwtDecoder decoder;
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.oauth2ResourceServer()
				.bearerTokenResolver(resolver)
				.jwt()
					.decoder(decoder)
		;
	}
}
```

ちなみにdecoderからメソッドチェインできる```jwtAuthenticationConverter```を実装することでコントローラーの```@AuthenticationPrincipal```から得られるオブジェクトを任意のものに変更できる。ユーザーの詳細情報をDBやRedisから引いたものを設定したり

## まとめ
残念ながら設定だけとは行かなかったが、ALBが行った認証を受け取って利用できることがわかった。
Issueを見ると、今後は```NimbusJwtDecoderJwkSupport```は非推奨になり、```OidcIdTokenDecoderFactory```に置き換わることで先ほどの署名アルゴリズムといったところが設定可能になるようだ。
https://github.com/spring-projects/spring-security/issues/6883


## ソースコード
https://github.com/tac-yacht/Sample_SpringSecurityForAWSALB

## 参考文献
* https://www.slideshare.net/masatoshitada7/oauth-20spring-security-51-121418814
* https://www.slideshare.net/masatoshitada7/spring-security-meetup
* https://qiita.com/suke_masa/items/0f75fa75a22a6551065b
* https://qiita.com/Yuki_BB3/items/fd410ef29935169aad9d
* https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-authenticate-users.html


## 余談
以下を読むとLet's EncryptはEC2はダメとのこと。ALBならドメイン違うし（elb.amazonaws.com）、リスナールールで適当に静的応答すれば行けるんじゃね?!→ダメでした。
https://hacknote.jp/archives/37697/
短期間でいいからAWS Certificate ManagerからXXX.elb.amazonaws.com向けの電子署名払い出してくれないかな……