3

I am writing a small application for myself and want to implement 2-factor authentication in Spring Boot. To do this, follow the tips of this article: https://www.baeldung.com/spring-security-two-factor-authentication-with-soft-token

Faced the following problems: 1) My code written based on this article does not work. Spring Security completely ignores is2FaEnabled == true and in any case authorizes the user, even if the code has not been entered.

Judging by the logs in this thread .authenticationDetailsSource (authenticationDetailsSource) does not even go for verification.

2) How can I implement the following: during authorization, first check if 2FA is enabled, if so, then direct the user to another URL or open a module with input and after correctly entering the code, authorize it?

Here is my source code:

CustomWebAuthenticationDetailsSource.java

@Component
public class CustomWebAuthenticationDetailsSource implements
        AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new CustomWebAuthenticationDetails(context);
    }
}

CustomWebAuthenticationDetails.java

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {

    @Getter
    private String verificationCode;

    public CustomWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        verificationCode = request.getParameter("code");
    }
}

CustomAuthenticationProvider.java

    public class CustomAuthenticationProvider extends DaoAuthenticationProvider {

        @Autowired
        private UserServiceImpl userServiceImpl;

        private Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);

        @Override
        public Authentication authenticate(Authentication auth)
                throws AuthenticationException {
        User user = userServiceImpl.findUserByEmail(auth.getName());

        String verificationCode
                = ((CustomWebAuthenticationDetails) auth.getDetails())
                .getVerificationCode();
        if ((user == null)) {
            throw new BadCredentialsException("Invalid username or password");
        }

        if (user.getIs2FaEnabled()) {
            Totp totp = new Totp(user.getTwoFaSecret());
            if (!isValidLong(verificationCode) || !totp.verify(verificationCode)) {
                throw new BadCredentialsException("Invalid verfication code");
            }
        }

        Authentication result = super.authenticate(auth);
        return new UsernamePasswordAuthenticationToken(
                user, result.getCredentials(), result.getAuthorities());
    }

    private boolean isValidLong(String code) {
        try {
            Long.parseLong(code);
        } catch (NumberFormatException e) {
            return false;
        }
        return true;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

WebSecurityConfig.java

    @Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {


    // n.b https://stackoverflow.com/questions/1018797/can-you-use-autowired-with-static-fields
    private static UserDetailsServiceImpl userDetailsService;

    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;

    @PostConstruct
    private void init() {
        userDetailsService = this.userDetailsServiceImpl;
    }

    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authProvider() {
        CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    // Backend configuration
    @Configuration
    @Order(1)
    public static class BackendConfigurationAdapter extends WebSecurityConfigurerAdapter {

        /*@Autowired
        private UserDetailsServiceImpl userDetailsService;*/

        @Autowired
        private CustomWebAuthenticationDetailsSource authenticationDetailsSource;

        public BackendConfigurationAdapter() {
            super();
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {

            http
                    .antMatcher("/admin/**")
                    .antMatcher("/admin/**/**")
                    .authorizeRequests()
                    .anyRequest()
                    .hasAuthority("ADMIN_PRIVILEGE")


                    .and()
                        .formLogin()
                        .authenticationDetailsSource(authenticationDetailsSource)
                        .loginPage("/admin/login")
                        .loginProcessingUrl("/admin/login")
                        .usernameParameter("email")
                        .passwordParameter("password")
                        .defaultSuccessUrl("/admin/dashboard")
                        .failureUrl("/admin/login?authError")
                        .permitAll()

                    .and()
                        .rememberMe()
                        .rememberMeParameter("remember-me")
                        .tokenValiditySeconds(86400)

                    .and()
                        .logout()
                        .logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
                        .logoutSuccessUrl("/admin/login")
                        .deleteCookies("JSESSIONID")

                    .and()
                        .exceptionHandling()
                        .accessDeniedPage("/403")

                    .and()
                        .csrf()
                        .ignoringAntMatchers("/admin/**");
        }

        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers(


                    "/backend/css/**",
                    "/backend/js/**",
                    "/backend/fonts/**",
                    "/backend/images/**",
                    "/backend/init/**"

            );
        }


        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }

    }


    // Frontend Configuration
    @Configuration
    @Order(2)
    public static class FrontendConfigurationAdapter extends WebSecurityConfigurerAdapter {


        @Autowired
        private CustomWebAuthenticationDetailsSource authenticationDetailsSource;

        public FrontendConfigurationAdapter() {
            super();
        }

        protected void configure(HttpSecurity http) throws Exception {

            http
                    .authorizeRequests().mvcMatchers("/robots.txt").permitAll()
                    .antMatchers(
                            "/", "/auth", "/signup", "/restore", "/activation/**",
                            "/admin/login", "/admin_restore",
                            "/attachments/get/**",
                            "/sendMessage",
                            "/error",
                            "/page/**",
                            "/categories", "/categories/**",
                            "/terms/**", "/posts", "/posts/**"
                    ).permitAll()
                    .anyRequest().authenticated()

                    .and()
                        .formLogin()
                        .authenticationDetailsSource(authenticationDetailsSource)
                        .loginPage("/auth")
                        .loginProcessingUrl("/auth")
                        .usernameParameter("email")
                        .passwordParameter("password")
                        .defaultSuccessUrl("/")
                        .failureUrl("/auth?authError")
                        .permitAll()

                    .and()
                        .rememberMe()
                        .rememberMeParameter("remember-me")
                        .tokenValiditySeconds(86400)

                    .and()
                        .oauth2Login().defaultSuccessUrl("/")

                    .and()
                        .logout()
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        .logoutSuccessUrl("/")
                        .deleteCookies("JSESSIONID")

                    .and()
                        .exceptionHandling()
                        .accessDeniedPage("/403")

                    .and()
                        .csrf()

                    .and()
                        .sessionManagement()
                        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)

                    .and()
                        .headers().frameOptions().sameOrigin();


        }

        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers(
                    "/frontend/css/**",
                    "/frontend/js/**",
                    "/frontend/fonts/**",
                    "/frontend/images/**",
                    "/frontend/lib/**",
                    "/frontend/vendor/**"
            );
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }



    }

}

Thanks in advance!

0 Answers0