TOTPソフトトークンを使用した多要素認証をAngular&Springアプリケーションに追加し、すべてをデフォルトのSpring Bootにできるだけ近づけます)セキュリティスターター。
トークンの検証はローカルで行われ(aerogear-otp-Javaライブラリを使用)、サードパーティのAPIプロバイダーは使用されません。
ユーザーのトークンの設定は機能しますが、Spring Security Authentication Manager/Providersを利用してトークンを検証することはできません。
APIにはエンドポイント_/auth/token
_があり、フロントエンドからユーザー名とパスワードを提供することでJWTトークンを取得できます。応答には認証ステータスも含まれ、これは[〜#〜] authenticated [〜#〜]またはPRE_AUTHENTICATED_MFA_REQUIREDのいずれかになります。
ユーザーがMFAを必要とする場合、_PRE_AUTHENTICATED_MFA_REQUIRED
_の単一の許可された権限と5分の有効期限でトークンが発行されます。これにより、ユーザーはエンドポイント_/auth/mfa-token
_にアクセスでき、AuthenticatorアプリからTOTPコードを提供して、完全に認証されたトークンを取得してサイトにアクセスできます。
MfaAuthenticationProvider
を実装するカスタムAuthenticationProvider
を作成しました:
_ @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// validate the OTP code
}
@Override
public boolean supports(Class<?> authentication) {
return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
_
そして、OneTimePasswordAuthenticationToken
を拡張してAbstractAuthenticationToken
を拡張し、ユーザー名(署名付きJWTから取得)とOTPコードを保持します。
カスタムWebSecurityConfigurerAdapter
があります。ここで、http.authenticationProvider()
を介してカスタムAuthenticationProvider
を追加します。 JavaDocによると、これは正しい場所のようです:
使用する追加のAuthenticationProviderを追加できます
私のSecurityConfig
の関連部分は次のようになります。
_ @Configuration
@EnableWebSecurity
@EnableJpaAuditing(auditorAwareRef = "appSecurityAuditorAware")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
public SecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(new MfaAuthenticationProvider());
http.authorizeRequests()
// Public endpoints, HTML, Assets, Error Pages and Login
.antMatchers("/", "favicon.ico", "/asset/**", "/pages/**", "/api/auth/token").permitAll()
// MFA auth endpoint
.antMatchers("/api/auth/mfa-token").hasAuthority(ROLE_PRE_AUTH_MFA_REQUIRED)
// much more config
_
AuthController
にはAuthenticationManagerBuilder
が挿入されており、すべてをまとめています。
_@RestController
@RequestMapping(AUTH)
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/mfa-token")
public ResponseEntity<Token> mfaToken(@Valid @RequestBody OneTimePassword oneTimePassword) {
var username = SecurityUtils.getCurrentUserLogin().orElse("");
var authenticationToken = new OneTimePasswordAuthenticationToken(username, oneTimePassword.getCode());
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// rest of class
_
ただし、_/auth/mfa-token
_に対して投稿すると、次のエラーが発生します。
_"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken
_
Spring Securityが認証プロバイダーを取得しないのはなぜですか?コントローラをデバッグすると、DaoAuthenticationProvider
がAuthenticationProviderManager
の唯一の認証プロバイダーであることがわかります。
私のMfaAuthenticationProvider
をBeanとして公開すると、登録されているのはonlyプロバイダーなので、反対の結果になります。
_No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
_
それで、どうすれば両方を入手できますか?
追加のAuthenticationProvider
をSpring Boot Security Starter構成されたシステムに統合して、両方を取得するための推奨される方法は何ですか、DaoAuthenticationProvider
と自分のカスタムMfaAuthenticationProvider
? Spring Boot Scurity Starterのデフォルトを維持し、独自のプロバイダーを追加したいと思います。
OTPアルゴリズム自体は、コードが有効なタイムスライス内のリプレイアタックから保護されないことを知っています。 RFC 6238はこれを明確にしています
検証者は、最初のOTPに対して検証が正常に発行された後、OTPの2回目の試行を受け入れてはいけません。
保護を実装するための推奨される方法があるかどうか疑問に思っていました。 OTPトークンは時間ベースなので、最後に成功したログインをユーザーのモデルに保存し、30秒のタイムスライスごとに1つのログインのみが成功することを確認します。もちろん、これはユーザーモデルの同期を意味します。より良いアプローチはありますか?
ありがとうございました。
-
PS:これはセキュリティに関する質問なので、信頼できる情報源や公式な情報源からの回答を探しています。ありがとうございました。
私自身の質問に答えるために、これは私がそれをさらに研究した後に実装した方法です。
AuthenticationProvider
を実装するプロバイダーをpojoとして使用しています。意図的にBean /コンポーネントではありません。それ以外の場合、Springはそれを唯一のプロバイダーとして登録します。
_public class MfaAuthenticationProvider implements AuthenticationProvider {
private final AccountService accountService;
@Override
public Authentication authenticate(Authentication authentication) {
// here be code
}
_
私のSecurityConfigで、SpringにAuthenticationManagerBuilder
を自動配線させ、MfaAuthenticationProvider
を手動で挿入しました
_@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
@Override
protected void configure(HttpSecurity http) throws Exception {
// other code
authenticationManagerBuilder.authenticationProvider(getMfaAuthenticationProvider());
// more code
}
// package private for testing purposes.
MfaAuthenticationProvider getMfaAuthenticationProvider() {
return new MfaAuthenticationProvider(accountService);
}
_
標準認証後、ユーザーがMFAを有効にすると、許可された権限PRE_AUTHENTICATED_MFA_REQUIREDで事前認証されます。これにより、単一のエンドポイント_/auth/mfa-token
_にアクセスできます。このエンドポイントは、有効なJWTと提供されたTOTPからユーザー名を取得し、authenticationManagerBuilderのauthenticate()
メソッドに送信します。authenticationManagerBuilderは、MfaAuthenticationProvider
を処理できるため、OneTimePasswordAuthenticationToken
を選択します。
_ var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
_