Безопасность Spring - ошибки вызывают проблемы с производительностью - PullRequest
0 голосов
/ 03 декабря 2018

У меня проблема с приложением SpringBoot.Мой ICS сообщает высокое время отклика по этому API , но мои метрики кода говорят, что все в порядке , например, запрос, отправляемый в мой API, занимает всего 50 мсек для запуска "делового" кодано эффективный ответ занимает более 500 мс!

Внутренние журналы моей JVM говорят, что при каждом запросе у меня появляется « UsernameNotFoundExcption », даже если учетные данные верны и API работает нормально.Вот почему я думаю, что моя проблема связана с уровнем SpringSecurity, но я не могу определить причину.

Моя точка входа:

@Component
public class BasicAuthenticationPoint extends BasicAuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
            throws IOException, ServletException {
        response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName());
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        writer.println("HTTP Status 401 - " + authEx.getMessage());
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        setRealmName("MYAPI");
        super.afterPropertiesSet();
    }
}

И мой адаптер:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private BasicAuthenticationPoint basicAuthenticationPoint;

    @Bean
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        HttpSecurity httpSec = http.csrf().disable();
        httpSec = httpSec.authorizeRequests()
                .antMatchers("/my-business-resources/**").hasRole("USER")
                .antMatchers("/actuator/**").hasRole("ADMIN")
                .and();
        httpSec.httpBasic().authenticationEntryPoint(basicAuthenticationPoint).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        // Load users file
        Resource confFile = resourceLoader.getResource("classpath:users.list");

        // Returns users from the configuration file <username, password (BCrypted), admin (boolean)>
        List<ApiUser> users = ApiUtils.getUsersFromFile(confFile);

        for(ApiUser u : users){
            // Add the username/password to the in-memory authentication manager
            if (u.admin)
                auth.inMemoryAuthentication().withUser(u.username).password(u.password).roles("USER", "ADMIN");
            else
                auth.inMemoryAuthentication().withUser(u.username).password(u.password).roles("USER");

        }
    }
}

Я что-то пропустил?

PS: Мое приложение Spring Boot упаковано как WAR-файл и выполняется на сервере Tomcat для целей стандартизации.

РЕДАКТИРОВАТЬ:

Вот полное имя стека имени пользователя NotFound (отформатировано в ICS):

Exception Details
Type:   UsernameNotFoundException
Exception Class:    org.springframework.security.core.userdetails.UsernameNotFoundException
API:    Exception
Thread Name:    https-openssl-apr-9343-exec-10 <522634598>

Exception StackTrace
Method  Class   Line    File Name
loadUserByUsername  org.springframework.security.provisioning.InMemoryUserDetailsManager    146 <unknown>
retrieveUser    org.springframework.security.authentication.dao.DaoAuthenticationProvider   104 <unknown>
authenticate    org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider   144 <unknown>
authenticate    org.springframework.security.authentication.ProviderManager 174 <unknown>
authenticate    org.springframework.security.authentication.ProviderManager 199 <unknown>
doFilterInternal    org.springframework.security.web.authentication.www.BasicAuthenticationFilter   180 <unknown>
doFilter    org.springframework.web.filter.OncePerRequestFilter 107 <unknown>
doFilter    org.springframework.security.web.FilterChainProxy$VirtualFilterChain    334 <unknown>
doFilter    org.springframework.security.web.authentication.logout.LogoutFilter 116 <unknown>
doFilter    org.springframework.security.web.FilterChainProxy$VirtualFilterChain    334 <unknown>
doFilterInternal    org.springframework.security.web.header.HeaderWriterFilter  66  <unknown>
doFilter    org.springframework.web.filter.OncePerRequestFilter 107 <unknown>
doFilter    org.springframework.security.web.FilterChainProxy$VirtualFilterChain    334 <unknown>
doFilter    org.springframework.security.web.context.SecurityContextPersistenceFilter   105 <unknown>
doFilter    org.springframework.security.web.FilterChainProxy$VirtualFilterChain    334 <unknown>
doFilterInternal    org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter 56  <unknown>
doFilter    org.springframework.web.filter.OncePerRequestFilter 107 <unknown>
doFilter    org.springframework.security.web.FilterChainProxy$VirtualFilterChain    334 <unknown>
doFilterInternal    org.springframework.security.web.FilterChainProxy   215 <unknown>
doFilter    org.springframework.security.web.FilterChainProxy   178 <unknown>
invokeDelegate  org.springframework.web.filter.DelegatingFilterProxy    357 <unknown>
doFilter    org.springframework.web.filter.DelegatingFilterProxy    270 <unknown>
internalDoFilter    org.apache.catalina.core.ApplicationFilterChain 193 <unknown>
doFilter    org.apache.catalina.core.ApplicationFilterChain 166 <unknown>
doFilter    org.springframework.boot.web.servlet.support.ErrorPageFilter    130 <unknown>
access$000  org.springframework.boot.web.servlet.support.ErrorPageFilter    66  <unknown>
doFilterInternal    org.springframework.boot.web.servlet.support.ErrorPageFilter$1  105 <unknown>
doFilter    org.springframework.web.filter.OncePerRequestFilter 107 <unknown>
doFilter    org.springframework.boot.web.servlet.support.ErrorPageFilter    123 <unknown>
internalDoFilter    org.apache.catalina.core.ApplicationFilterChain 193 <unknown>
doFilter    org.apache.catalina.core.ApplicationFilterChain 166 <unknown>
filterAndRecordMetrics  org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter    155 <unknown>
filterAndRecordMetrics  org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter    123 <unknown>
doFilterInternal    org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter    108 <unknown>
doFilter    org.springframework.web.filter.OncePerRequestFilter 107 <unknown>
internalDoFilter    org.apache.catalina.core.ApplicationFilterChain 193 <unknown>
doFilter    org.apache.catalina.core.ApplicationFilterChain 166 <unknown>
doFilterInternal    org.springframework.web.filter.CharacterEncodingFilter  200 <unknown>
doFilter    org.springframework.web.filter.OncePerRequestFilter 107 <unknown>
internalDoFilter    org.apache.catalina.core.ApplicationFilterChain 193 <unknown>

Ответы [ 3 ]

0 голосов
/ 03 декабря 2018

Я полагаю, что причина в том, что это занимает 500 мс, связана с кодированием пароля.BCrypt - это механизм кодирования, разработанный для того, чтобы сделать аутентификацию более длительной, чтобы смягчить атаки методом "грубой силы".

Как правило, эта проблема производительности решается путем обмена долговременными учетными данными (например, паролями) с кратковременными, такими какжетоны. OAuth - это структура, которая пытается стандартизировать эту практику. В Spring Security 5.1 появилась поддержка защиты API с помощью OAuth 2.0 .Это не сразу решит проблему с производительностью, так как может потребовать внедрения большей инфраструктуры, но, вероятно, это лучший выбор для обеспечения безопасности в долгосрочной перспективе.

В качестве альтернативы вы можете использовать более слабый кодер или, возможно, меньшее количествораунды BCrypt, но обратите внимание, что оба они являются компромиссом между безопасностью и производительностью.Вы бы заменили свой BCryptPasswordEncoder на что-то другое, например:

@Bean
public PasswordEncoder encoder() {
    // ...
}

РЕДАКТИРОВАТЬ : Рад, что вы нашли проблему!Тангенциально, я бы порекомендовал очистить то, как вы описываете свой менеджер аутентификации.Его можно заменить на UserDetailsService:

@Bean
@Override
public UserDetailsService userDetailsService() {
    Resource confFile = resourceLoader.getResource("classpath:users.list");
    Collection<UserDetails> users = ApiUtils.getUsersFromFile(confFile)
        .stream().map(this::toUserDetails)
        .collect(Collectors.toList());
    return new InMemoryUserDetailsManager(users);
}

private UserDetails toUserDetails(ApiUser apiUser) {
    UserBuilder builder = User.builder()
        .username(apiUser.username)
        .password(apiUser.password);
    return apiUser.admin ?
        builder.roles("USER", "ADMIN").build() :
        builder.roles("USER").build();
}

Это хорошо, потому что он занимает меньше места в конфигурации (диспетчер аутентификации - это «больше», чем служба сведений о пользователе).Если вы просто хотите предоставить пользователям, то более типично переопределить UserDetailsService вместо AuthenticationManager.

0 голосов
/ 04 декабря 2018

Хорошо, после большой отладки я обнаружил проблему!

Проблема в коде, который я использую для заполнения аутентификатора в памяти:

for(ApiUser u : users){
    // Add the username/password to the in-memory authentication manager
    if (u.admin)
        auth.inMemoryAuthentication().withUser(u.username).password(u.password).roles("USER", "ADMIN");
    else
        auth.inMemoryAuthentication().withUser(u.username).password(u.password).roles("USER");
}

Факт вызоваauth.inMemoryAuthentication() много раз создает новый InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> каждый раз ( Spring code ), и поэтому у нас столько же AuthenticationManager, сколько пользователей (по одному пользователю на каждого), поэтому процесс аутентификации выполняется несколько разраз для каждого запроса.

Вот как я исправил ошибку:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    // Load users file
    Resource confFile = resourceLoader.getResource("classpath:users.list");

    // Returns users from the configuration file <username, password (BCrypted), admin (boolean)>
    List<ApiUser> users = ApiUtils.getUsersFromFile(confFile);

    @SuppressWarnings("rawtypes")
    UserDetailsBuilder udb = null;

    for(ApiUser u : users){
        // Add the username/password to the in-memory authentication manager
        if (udb == null)
            udb = auth.inMemoryAuthentication().withUser(u.username).password(u.password);
        else
            udb = udb.and().withUser(u.username).password(u.password);

        if (u.admin)
            udb.roles("USER", "ADMIN");
        else
            udb.roles("USER");
    }
}

Теперь мое среднее время ответа составляет 80 мс

0 голосов
/ 03 декабря 2018

Вы также выполняете проверки безопасности своих ресурсов и других файлов.Вы должны исключить их для аутентификации вообще.

Посмотрите здесь: Spring Security: как исключить определенные ресурсы?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...