Spring Boot 2 및 Spring Security 5를 사용한 다중 요소 인증


11

가능한 한 모든 것을 Spring Boot Security Starter 의 기본값에 가깝게 유지하면서 TOTP 소프트 토큰을 사용하는 다단계 인증을 Angular & Spring 애플리케이션에 추가하고 싶습니다 .

토큰 유효성 검사는 타사 API 공급자가 아닌 로컬 (aerogear-otp-java 라이브러리 사용)에서 발생합니다.

사용자를위한 토큰 설정은 작동하지만 Spring Security Authentication Manager / 제공자를 활용하여 유효성을 검증하는 것은 아닙니다.

TL; DR

  • 추가 AuthenticationProvider를 Spring Boot Security Starter 구성 시스템 에 통합하는 공식적인 방법은 무엇입니까 ?
  • 재생 공격을 방지하기 위해 권장되는 방법은 무엇입니까?

긴 버전

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);
    }

그리고는 (서명 된 JWT에서 가져온) 사용자 이름과 OTP 코드를 보유하도록 OneTimePasswordAuthenticationToken확장 AbstractAuthenticationToken됩니다.

구성

나는 내 사용자 지정이 WebSecurityConfigurerAdapter내 사용자 정의를 추가 AuthenticationProvider로를 http.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있습니다.

MfaAuthenticationProviderBean을 공개하면 등록 된 유일한 제공자이므로 반대가됩니다.

No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken. 

그래서 어떻게 두 가지를 얻을 수 있습니까?

내 질문

추가를 통합하는 권장 방법은 무엇입니까 AuthenticationProvider봄 부트 보안 스타터 구성된 시스템은, 그래서 모두를 얻을의 DaoAuthenticationProvider내 자신의 정의 MfaAuthenticationProvider? Spring Boot Scurity Starter 의 기본값을 유지하고 추가로 자체 공급자를 갖고 싶습니다.

리플레이 공격 방지

OTP 알고리즘 자체가 코드가 유효한 타임 슬라이스 내에서 재생 공격으로부터 보호하지 못한다는 것을 알고 있습니다. RFC 6238이이를 명확하게합니다.

검증자는 첫 번째 OTP에 대해 성공적인 검증이 발행 된 후 OTP의 두 번째 시도를 수락해서는 안되며, 이는 OTP의 일회성 사용을 보장합니다.

보호를 구현하는 권장 방법이 있는지 궁금합니다. OTP 토큰은 시간 기반이므로 사용자의 모델에 마지막으로 성공한 로그인을 저장하고 30 초마다 하나의 성공적인 로그인이 있는지 확인하려고합니다. 이것은 물론 사용자 모델에서의 동기화를 의미합니다. 더 나은 접근 방법이 있습니까?

감사합니다.

-

추신 : 이것은 보안에 관한 질문이므로 신뢰할 수 있고 공식적인 출처에서 답을 찾고 있습니다. 감사합니다.

답변:


0

내 자신의 질문에 대답하기 위해, 이것은 추가 연구 후에 어떻게 구현했는지입니다.

구현하는 pojo로 공급자가 있습니다 AuthenticationProvider. 의도적으로 Bean / Component가 아닙니다. 그렇지 않으면 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로부터 아이디를 취하고로 전송 authenticate()authenticationManagerBuilder 선택하는, 방법 MfaAuthenticationProvider은 처리 할 수 있기 때문에 OneTimePasswordAuthenticationToken.

    var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
    var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.