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

認証が辛い!! もっと楽に安全に認証したい。したいですよね?


そういえば、ALBがOIDC認証に対応していたな、Spring Securityに食わせれば手間いらずで認証できるんじゃ・・・?


事前準備


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


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に以下を設定すること(決め打ちとする場合はこの限りでは無い)


application.yml

spring:

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


JwtDecoderへの取得処理の設定

実体は前述したとおりNimbusJwtDecoderJwkSupportなのだが、任意の署名アルゴリズムと鍵取得が設定できない。

そのためまるごとコピーして以下の部分に先ほどのKeySelectorで初期化する。


private_constractor(String,String)

        JWSKeySelector<SecurityContext> jwsKeySelector =

new ALBPublicKeySelector<SecurityContext>(this.jwsAlgorithm, jwkSource); //ここ

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


public_constractor

    public ALBJwtDecoder(@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUrl) {

this(jwkSetUrl, JwsAlgorithms.ES256);
}


処理利用の設定

あとはほかのSpring Security同様ConfigJavaを記述


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


参考文献


余談

以下を読むとLet's EncryptはEC2はダメとのこと。ALBならドメイン違うし(elb.amazonaws.com)、リスナールールで適当に静的応答すれば行けるんじゃね?!→ダメでした。

https://hacknote.jp/archives/37697/

短期間でいいからAWS Certificate ManagerからXXX.elb.amazonaws.com向けの電子署名払い出してくれないかな……