Как настроить Spring Security AuthenticationManager для использования с JdbcUserDetailsManager? - PullRequest
0 голосов
/ 31 марта 2020

У меня есть веб-приложение, которое использует Spring Boot и Security, настроенное на использование входа в форму с аутентификацией JDB C.

Вход и выход из системы работают нормально, и в целом аутентификация работает.

За исключением одного случая ... когда я пытаюсь изменить свой пароль, я замечаю, что хотя само изменение пароля происходит успешно, AuthenticationManager, в котором я хочу проверить существующий пароль ..., имеет значение null!

enter image description here

Как я могу настроить AuthenticationManager (возможно, с DaoAuthenticationProvider и / или DaoAuthenticationManager?), Чтобы AuthenticationManager не был нулевым и проверил существующий пароль?

Соответствующая конфигурация:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private RESTAuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private RESTAuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private RESTLogoutSuccessHandler restLogoutSuccessHandler;

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/h2-console/**")
                .permitAll();
        httpSecurity.authorizeRequests().antMatchers("/auth/**").authenticated();
        httpSecurity.cors().configurationSource(corsConfigurationSource());
        httpSecurity.csrf()
                .ignoringAntMatchers("/h2-console/**")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        httpSecurity.headers()
                .frameOptions()
                .sameOrigin();
        httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        httpSecurity.formLogin().successHandler(authenticationSuccessHandler);
        httpSecurity.formLogin().failureHandler(authenticationFailureHandler);
        httpSecurity.logout().logoutSuccessHandler(restLogoutSuccessHandler);
    }

    @Autowired
    @Bean
    public UserDetailsManager configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
                .dataSource(dataSource)
                .withDefaultSchema();

        jdbcUserDetailsManagerConfigurer.withUser("user1")
                .password(passwordEncoder().encode("user1"))
                .roles("USER");

        return jdbcUserDetailsManagerConfigurer.getUserDetailsService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12); // Strength increased as per OWASP Password Storage Cheat Sheet
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT"));
        configuration.setAllowedHeaders(List.of("X-XSRF-TOKEN", "Content-Type"));
        configuration.setExposedHeaders(List.of("Content-Disposition"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        source.registerCorsConfiguration("/login", configuration);
        source.registerCorsConfiguration("/logout", configuration);
        return source;
    }
}

AuthController, и здесь я хочу, чтобы UserDetailsManager специально вводился - чтобы можно было легко сменить пароль учетной записи:

import org.adventure.inbound.ChangePasswordData;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;

@RestController
@CrossOrigin(origins = "http://localhost:4200")
@RequestMapping("/auth")
public class AuthController {
    private UserDetailsManager userDetailsManager;
    private PasswordEncoder passwordEncoder;

    public AuthController(UserDetailsManager userDetailsManager, PasswordEncoder passwordEncoder) {
        this.userDetailsManager = userDetailsManager;
        this.passwordEncoder = passwordEncoder;
    }

    @PreAuthorize("hasRole('USER')")
    @PutMapping(path = "changePassword", consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> changePassword(@RequestBody ChangePasswordData changePasswordData, Principal principal) {
        if (principal == null) {
            return ResponseEntity.badRequest().body("Only logged in users may change their password");
        } else {
            if (changePasswordData.getCurrentPassword() == null || changePasswordData.getNewPassword() == null) {
                return ResponseEntity.badRequest().body("Either of the supplied passwords was null");
            } else {
                String encodedPassword = passwordEncoder.encode(changePasswordData.getNewPassword());
                userDetailsManager.changePassword(
                        changePasswordData.getCurrentPassword(), encodedPassword);
                return ResponseEntity.ok().build();
            }
        }
    }
}

Если я попытаюсь Конфигурация, упомянутая ниже в ответе, я получаю:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in org.adventure.controllers.AuthController required a  
bean of type 'org.springframework.security.provisioning.UserDetailsManager' that  
could not be found.

The following candidates were found but could not be injected:
    - Bean method 'inMemoryUserDetailsManager' in  
    'UserDetailsServiceAutoConfiguration' not loaded because @ConditionalOnBean  
    (types: org.springframework.security.authentication.AuthenticationManager,  
    org.springframework.security.authentication.AuthenticationProvider,  
    org.springframework.security.core.userdetails.UserDetailsService,  
    org.springframework.security.oauth2.jwt.JwtDecoder  
    org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;  
    SearchStrategy: all) found beans of type  
    'org.springframework.security.core.userdetails.UserDetailsService' userDetailsServiceBean

Ответы [ 3 ]

0 голосов
/ 01 апреля 2020

Ваша установка имеет недостатки, поскольку вы включаете слишком раннее создание UserDetailsService того, что вы должны делать

  1. Ваш configureGlobal метод должен вернуть void.
  2. Переопределите userDetailsServiceBean, позвоните super и аннотируйте это с помощью @Bean. Как задокументировано здесь
  3. Вы должны установить passwordEncoder в своей конфигурации userDetailsService и не кодировать пароль самостоятельно при создании пользователя.
  4. Вам следует также включите в свой класс @EnableWebSecurity.

Таким образом Spring Security будет правильно инициализировать и настроить все компоненты. (Хотя 3 и 4 не связаны, они должны быть установлены для правильной конфигурации в общем смысле).

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private RESTAuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private RESTAuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private RESTLogoutSuccessHandler restLogoutSuccessHandler;

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/h2-console/**")
                .permitAll();
        httpSecurity.authorizeRequests().antMatchers("/auth/**").authenticated();
        httpSecurity.cors().configurationSource(corsConfigurationSource());
        httpSecurity.csrf()
                .ignoringAntMatchers("/h2-console/**")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        httpSecurity.headers()
                .frameOptions()
                .sameOrigin();
        httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        httpSecurity.formLogin().successHandler(authenticationSuccessHandler);
        httpSecurity.formLogin().failureHandler(authenticationFailureHandler);
        httpSecurity.logout().logoutSuccessHandler(restLogoutSuccessHandler);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
                .dataSource(dataSource)
                .passwordEncoder(passwordEncoder());
                .withDefaultSchema();

        jdbcUserDetailsManagerConfigurer.withUser("user1")
                .password("user1")
                .roles("USER");

    }

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() {
        super.userDetailsServiceBean();
    }

}
0 голосов
/ 03 апреля 2020

Этот конфиг работает нормально. При смене паролей существующий пользователь аутентифицируется с помощью DaoAuthenticationProvider, который имеет ссылку на JdbcUserDetailsManager, который используется AuthController

enter image description here enter image description here

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private RESTAuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private RESTAuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private RESTLogoutSuccessHandler restLogoutSuccessHandler;

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/h2-console/**")
                .permitAll();
        httpSecurity.authorizeRequests().antMatchers("/auth/**").authenticated();
        httpSecurity.cors().configurationSource(corsConfigurationSource());
        httpSecurity.csrf()
                .ignoringAntMatchers("/h2-console/**")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        httpSecurity.headers()
                .frameOptions()
                .sameOrigin();
        httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        httpSecurity.formLogin().successHandler(authenticationSuccessHandler);
        httpSecurity.formLogin().failureHandler(authenticationFailureHandler);
        httpSecurity.logout().logoutSuccessHandler(restLogoutSuccessHandler);
    }

    @Autowired
    @Bean
    public JdbcUserDetailsManager configureGlobal(AuthenticationManager authenticationManager,
                                                  AuthenticationManagerBuilder auth) throws Exception {
        JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
                .dataSource(dataSource)
                .passwordEncoder(passwordEncoder())
                .withDefaultSchema();

        jdbcUserDetailsManagerConfigurer.withUser("user1")
                .password(passwordEncoder().encode("user1"))
                .roles("USER");

        JdbcUserDetailsManager jdbcUserDetailsManager = jdbcUserDetailsManagerConfigurer.getUserDetailsService();
        jdbcUserDetailsManager.setAuthenticationManager(authenticationManager);

        return jdbcUserDetailsManager;
    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12); // Strength increased as per OWASP Password Storage Cheat Sheet
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT"));
        configuration.setAllowedHeaders(List.of("X-XSRF-TOKEN", "Content-Type"));
        configuration.setExposedHeaders(List.of("Content-Disposition"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        source.registerCorsConfiguration("/login", configuration);
        source.registerCorsConfiguration("/logout", configuration);
        return source;
    }
}
0 голосов
/ 31 марта 2020

Не уверен, почему это так, но вы не могли бы просто сделать что-то похожее на это, вместо того, чтобы пройти через authmanager: Непосредственно работать с текущим вошедшим пользователем (подробности).

   @Autowired
    private PasswordEncoder passwordEncoder;

    public void changeUserPassword(@AuthenticationPrincipal UserDetails userDetails // whatever your userdetails implementation is..
                                               String newPassword,
                                               String oldPassword) {

        if (userDetails == null) {
            // user not logged in
        }

        String currentEncryptedPassword = userDetails.getPassword();
        if (!currentEncryptedPassword.equals(passwordEncoder.encode(oldPassword))) {
            // wrong password
        }

        dao.updatepasswd(user, passwordencoder.encode(newsPassword)) //change password in db..
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...