Багатофакторна автентифікація за допомогою Spring Boot 2 та Spring Security 5


11

Я хочу додати багатофакторну автентифікацію з м'якими маркерами TOTP до програми Angular & Spring, зберігаючи все як можна ближче до стандартних параметрів Spring Boot Security Starter .

Перевірка токена відбувається локально (за допомогою бібліотеки aerogear-otp-java), без стороннього постачальника API.

Налаштування жетонів для користувача працює, але перевірка їх шляхом використання диспетчера / постачальників Spring Security Authentication Manager не робить.

TL; DR

  • Який офіційний спосіб інтегрувати додатковий AuthenticationProvider в налаштовану систему Spring Boot Security Starter ?
  • Які рекомендовані способи запобігти повторним атакам?

Довга версія

API має кінцеву точку, /auth/tokenз якої інтерфейс може отримати маркер JWT, надавши ім'я користувача та пароль. Відповідь також включає в себе аутентифікацію-статус, який може бути або аутентифікований або PRE_AUTHENTICATED_MFA_REQUIRED .

Якщо користувачеві потрібен MFA, маркер видається з одним наданим повноваженням PRE_AUTHENTICATED_MFA_REQUIREDта терміном дії 5 хвилин. Це дозволяє користувачеві отримати доступ до кінцевої точки, /auth/mfa-tokenде вони можуть надати код TOTP від ​​програми Authenticator і отримати повністю автентифікований маркер для доступу до сайту.

Провайдер і токен

Я створив свій звичай, 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, куди я додаю свій звичай 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.

Якщо я виставляю свою MfaAuthenticationProviderяк боб, це єдиний зареєстрований Постачальник, тож я отримую навпаки:

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

Отже, як я можу отримати обидва?

Моє запитання

Який рекомендований спосіб інтегрувати додаткову систему AuthenticationProviderв налаштовану систему Spring Boot Security Starter , щоб я отримав DaoAuthenticationProviderі власний , і власний звичай MfaAuthenticationProvider? Я хочу зберегти стандартні налаштування Spring Boot Security і додатково мати власного постачальника.

Попередження нападу відтворення

Я знаю, що алгоритм OTP сам по собі не захищає від атак повторного відтворення в проміжок часу, в якому код дійсний; RFC 6238 робить це зрозумілим

Верифікатор НЕ МОЖЕ приймати другу спробу OTP після того, як успішна перевірка була видана для першого OTP, що забезпечує одноразове використання OTP.

Мені було цікаво, чи є рекомендований спосіб здійснення захисту. Оскільки маркери OTP засновані на часі, я думаю про збереження останнього успішного входу на модель користувача та переконайтесь, що є лише один успішний логін за 30 секунд. Це звичайно означає синхронізацію на моделі користувача. Будь-які кращі підходи?

Дякую.

-

PS: оскільки це питання про безпеку, я шукаю відповідь, отриману з надійних та / або офіційних джерел. Дякую.

Відповіді:


0

Щоб відповісти на моє власне питання, саме так я його реалізував після подальших досліджень.

У мене є постачальник, як піхо, який реалізує AuthenticationProvider. Це навмисно не боб / компонент. Інакше 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()методу аутентифікаціїManagerBuilder, який вибирає те, MfaAuthenticationProviderяк воно може працювати OneTimePasswordAuthenticationToken.

    var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
    var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.