0

Закодированный пароль не выглядит как BCrypt

13

Проблема с аутентификацией в Spring Boot с использованием OAuth2 и JWT

Я использую Spring Boot, Spring Security, OAuth2 и JWT для аутентификации своего приложения, но постоянно получаю ошибку, не понимая, в чем дело. Вот моя реализация класса CustomDetailsService:

@Service
public class CustomDetailsService implements UserDetailsService {
    private static final Logger logger = LoggerFactory.getLogger(CustomDetailsService.class);

    @Autowired
    private UserBO userBo;

    @Autowired
    private RoleBO roleBo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AppUsers appUsers = null;
        try {
            appUsers = this.userBo.loadUserByUsername(username);
            System.out.println("========|||=========== " + appUsers.getUsername());
        } catch (IndexOutOfBoundsException e) {
            throw new UsernameNotFoundException("Неверное имя пользователя");
        } catch (DataAccessException e) {
            e.printStackTrace();
            throw new UsernameNotFoundException("Ошибка базы данных");
        } catch (Exception e) {
            e.printStackTrace();
            throw new UsernameNotFoundException("Неизвестная ошибка");
        }

        if (appUsers == null) {
            throw new UsernameNotFoundException("Неверные учетные данные");
        }
        logger.info("Имя пользователя: " + appUsers.getUsername());
        return buildUserFromUserEntity(appUsers);
    }

    private User buildUserFromUserEntity(AppUsers authUsers) {
        Set<UserRole> userRoles = authUsers.getUserRoles();

        boolean enabled = true;
        boolean accountNotExpired = true;
        boolean credentialsNotExpired = true;
        boolean accountNotLocked = true;

        if (authUsers.getAccountIsActive()) {
            try {
                if (authUsers.getAccountExpired()) {
                    accountNotExpired = true;
                } else if (authUsers.getAccountIsLocked()) {
                    accountNotLocked = true;
                } else {
                    if (containsRole(userRoles, roleBo.findRoleByName("FLEX_ADMIN"))) {
                        accountNotLocked = false;
                    }
                }
            } catch (Exception e) {
                enabled = false;
                e.printStackTrace();
            }
        } else {
            accountNotExpired = false;
        }
        // конвертируем модель пользователя в пользователя Spring Security
        String username = authUsers.getUsername();
        String password = authUsers.getPassword();

        List<GrantedAuthority> authorities = buildUserAuthority(userRoles);

        User springUser = new User(username, password, enabled, accountNotExpired, credentialsNotExpired, accountNotLocked, authorities);
        return springUser;
    }
}

Конфигурация OAuth2

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public JwtAccessTokenConverter tokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey(PRIVATE_KEY);
        tokenConverter.setVerifierKey(PUBLIC_KEY);
        return tokenConverter;
    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(tokenConverter());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpointsConfigurer) throws Exception {
        endpointsConfigurer.authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .accessTokenConverter(tokenConverter());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
        securityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient(CLIENT_ID)
                .secret(CLIENT_SECRET)
                .scopes("read", "write")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(20000)
                .refreshTokenValiditySeconds(20000);
    }
}

Конфигурация безопасности

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    CustomDetailsService customDetailsService;

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

    @Override
    @Autowired
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(customDetailsService).passwordEncoder(encoder());
        System.out.println("Сделано... закончено");
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.NEVER);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManagerBean();
    }
}

Ошибки и логи

В логах нет сообщения об ошибках, кроме следующего:

Hibernate: select appusers0_.id as id1_2_, appusers0_.account_expired as account_2_2_, ...
Tinubu
2018-03-31 01:42:03.255  INFO 4088 --- [nio-8072-exec-2] o.a.c.c.BCryptPasswordEncoder     : Закодированный пароль не выглядит как BCrypt

Модель сущности

В моей модели сущности пароль в базе данных правильно зашифрован с помощью BCrypt и имеет тип varchar(255), что превышает 60 символов.

Вот код сущности AppUsers:

@Entity
@Table(name="USERS")
@DynamicUpdate
public class AppUsers {
    @Id
    @Column(name="ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="username")
    private String username;

    @Column(name="password")
    private String password;

    @OneToMany(mappedBy="appUsers")
    private Set<UserRole> userRoles;

    // сеттеры и геттеры
}

Пожалуйста, помогите разобраться, какая может быть причина возникновения этого предупреждения и как можно решить проблему с аутентификацией.

5 ответ(ов)

0

Когда BCryptPasswordEncoder не может сопоставить открытый пароль с кодированным паролем, он выдает это предупреждение. Причина может заключаться в том, что зашифрованный пароль имеет префикс $2b или $2y.

Однако, в Spring Security есть ошибка, связанная с регулярным выражением, которое всегда ищет $2a. Это может привести к проблемам при проверке паролей, закодированных с помощью более новых версий BCrypt.

Чтобы устранить эту проблему, вы можете установить точку останова в методе matches() класса BCryptPasswordEncoder. Это позволит вам отладить код и глубже понять, где именно происходит сбой сопоставления.

Рекомендуется обновить версию Spring Security, если проблема сохраняется, так как развитие данной библиотекой может привести к исправлению данной недоработки.

0

Да, в приведенном вами коде используется метод passwordEncoder.encode(clientSecret), который отвечает за кодирование клиентского секрета. Убедитесь, что объект passwordEncoder корректно инициализирован и использует тот же алгоритм хеширования, что и ваша система аутентификации.

Если вы по-прежнему сталкиваетесь с проблемами, проверьте следующие моменты:

  1. Убедитесь, что clientSecret правильно передан и не является пустым значением.
  2. Проверьте, правильно ли он закодирован при сохранении и правильно ли сравнивается при проверке.
  3. Убедитесь, что алгоритм хеширования (например, BCrypt или другой) соответствует тому, что ожидается в процессе аутентификации.

Если все эти аспекты в порядке, тогда ваш клиентский секрет кодируется корректно.

0

Вы можете настроить PasswordEncoder следующим образом:

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

Этот код создает бин PasswordEncoder, который будет использовать делегирующий механизм для поддержки различных алгоритмов шифрования паролей. Используйте этот подход для обработки паролей в вашем приложении.

0

Проблема, с которой вы столкнулись, связана с тем, что зависимости OAuth2 были перемещены в фреймворк Spring Cloud. Ранее вы использовали следующую зависимость из Spring Security:

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

Теперь же, если вы используете Spring Cloud (например, версию Finchley.RELEASE), вам нужно подключить следующую зависимость:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

Также обратите внимание, что теперь вам необходимо закодировать секрет вашего клиента. Пример кода для этого может выглядеть следующим образом:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients
            .inMemory()
            .withClient("clientapp")
            .authorizedGrantTypes("password", "refresh_token")
            .authorities("USER")
            .scopes("read", "write")
            .resourceIds(RESOURCE_ID)
            .secret(passwordEncoder.encode("SECRET"));
}

Убедитесь, что вы используете passwordEncoder для шифрования вашего секрета, так как это важно для обеспечения безопасности вашего приложения. Если у вас возникли дополнительные вопросы по настройке, не стесняйтесь спрашивать!

0

На текущий момент, с версией Spring Boot 2.1.7.RELEASE, я все еще сталкиваюсь с этой проблемой. Я использовал несколько онлайн-инструментов, которые генерировали хеши, начинающиеся с $2b или $2y, которые не поддерживаются BCryptPasswordEncoder в Spring:

public class BCryptPasswordEncoder implements PasswordEncoder {
    private Pattern BCRYPT_PATTERN = Pattern
            .compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
    ...
}

Решение: используйте класс BCryptPasswordEncoder, чтобы зашифровать пароль:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("admin"));

Затем добавьте это в конфигурацию:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
        throws Exception {
    auth.inMemoryAuthentication()
            .withUser("admin")
            .password("{bcrypt}$2a$10$6CW1agMzVzBhxDzK0PcxrO/cQcmN9h8ZriVEPy.6DJbVeyATG5mWe")
            .roles("ADMIN");
}

Таким образом, вы сможете успешно использовать BCryptPasswordEncoder для шифрования паролей и устранить проблемы с хешами, начинающимися с $2b или $2y.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь