認証が辛い!! もっと楽に安全に認証したい。したいですよね?
そういえば、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
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に以下を設定すること(決め打ちとする場合はこの限りでは無い)
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://public-keys.auth.elb.us-west-2.amazonaws.com/
JwtDecoderへの取得処理の設定
実体は前述したとおりNimbusJwtDecoderJwkSupport
なのだが、任意の署名アルゴリズムと鍵取得が設定できない。
そのためまるごとコピーして以下の部分に先ほどのKeySelectorで初期化する。
JWSKeySelector<SecurityContext> jwsKeySelector =
new ALBPublicKeySelector<SecurityContext>(this.jwsAlgorithm, jwkSource); //ここ
アルゴリズムは以下の部分を書き換える
public ALBJwtDecoder(@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUrl) {
this(jwkSetUrl, JwsAlgorithms.ES256);
}
処理利用の設定
あとはほかのSpring Security同様ConfigJavaを記述
@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://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向けの電子署名払い出してくれないかな……