Подтвердить что ты не робот

Spring переключатель безопасности для полномочий аутентификации Ldap и базы данных

Я реализовал аутентификацию базы данных для своей веб-страницы и веб-службы. Он хорошо работает для обоих, теперь мне нужно добавить аутентификацию Ldap. Я должен пройти аутентификацию через удаленный сервер Ldap (используя имя пользователя и пароль), и если пользователь существует, я должен использовать свою базу данных для ролей пользователей (в моей базе данных имя пользователя совпадает с именем пользователя Ldap). Поэтому мне нужно перейти от моего фактического кода к Ldap и аутентификации базы данных, как описано выше. Мой код: Класс SecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("userDetailsService")
    UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

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

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
             http.csrf().disable()
             .antMatcher("/client/**")
             .authorizeRequests()
             .anyRequest().authenticated()
             .and()
             .httpBasic();
        }
    }

    @Configuration
    @Order(2)
    public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{

        @Override
        public void configure(WebSecurity web) throws Exception {
            web
            //Spring Security ignores request to static resources such as CSS or JS files.
            .ignoring()
            .antMatchers("/static/**");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
            .authorizeRequests() //Authorize Request Configuration
                //the / and /register path are accepted without login
                //.antMatchers("/", "/register").permitAll()
                //the /acquisition/** need admin role
                //.antMatchers("/acquisition/**").hasRole("ADMIN")
                //.and().exceptionHandling().accessDeniedPage("/Access_Denied");
                //all the path need authentication
                .anyRequest().authenticated()
                .and() //Login Form configuration for all others
            .formLogin()
                .loginPage("/login")
                //important because otherwise it goes in a loop because login page require authentication and authentication require login page
                    .permitAll()
            .and()
            .logout()
                .logoutSuccessUrl("/login?logout")
                .permitAll();
             // CSRF tokens handling
        }
    }

Класс MyUserDetailsService

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserServices userServices;
    static final Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class);

    @Transactional(readOnly=true)
    @Override
    public UserDetails loadUserByUsername(final String username){
        try{
            com.domain.User user = userServices.findById(username);
            if (user==null)
                LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : User doesn't exist" ); 
            else{
                List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
                return buildUserForAuthentication(user, authorities);
            }
        }catch(Exception e){
            LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : " + ErrorExceptionBuilder.buildErrorResponse(e));  }
        return null;
    }

    // Converts com.users.model.User user to
    // org.springframework.security.core.userdetails.User
    private User buildUserForAuthentication(com.domain.User user, List<GrantedAuthority> authorities) {
        return new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, authorities);
    }

    private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {

        Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

        // Build user authorities
        for (UserRole userRole : userRoles) {
            setAuths.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
        }

        List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);

        return Result;
    }

поэтому я должен:

1) доступ пользователя к странице входа для веб-страниц и имени пользователя и пароля для веб-служб. Это нужно сделать через Ldap.

2) имя пользователя пользователя для запроса базы данных для аутентификации пользователя. Вы знаете, как я могу это реализовать? Благодаря

ОБНОВЛЕНИЕ С ПРАВИЛЬНЫМ КОДОМ: После @M. Рекомендации Deinum Я создаю класс MyAuthoritiesPopulator вместо MyUserDetailsService, и аутентификация с базой данных и Ldap работает:

    @Service("myAuthPopulator")
public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {

    @Autowired
    private UserServices userServices;
    static final Logger LOG = LoggerFactory.getLogger(MyAuthoritiesPopulator.class);

    @Transactional(readOnly=true)
    @Override
    public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        try{
            com.domain.User user = userServices.findById(username);
            if (user==null)
                LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : User doesn't exist into ATS database" );  
            else{
                for(UserRole userRole : user.getUserRole()) {
                    authorities.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
                }
                return authorities;
            }
        }catch(Exception e){
            LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
        return authorities;
    }
}

и я изменил SecurityConfig, как показано ниже:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("myAuthPopulator")
    LdapAuthoritiesPopulator myAuthPopulator;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

         auth.ldapAuthentication()
          .contextSource()
            .url("ldap://127.0.0.1:10389/dc=example,dc=com")
//          .managerDn("")
//          .managerPassword("")
          .and()   
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .ldapAuthoritiesPopulator(myAuthPopulator);     
    }

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
             http.csrf().disable()
             .antMatcher("/client/**")
             .authorizeRequests()
             //Excluede send file from authentication because it doesn't work with spring authentication
             //TODO add java authentication to send method
             .antMatchers(HttpMethod.POST, "/client/file").permitAll()
             .anyRequest().authenticated()
             .and()
             .httpBasic();
        }
    }

    @Configuration
    @Order(2)
    public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{

        @Override
        public void configure(WebSecurity web) throws Exception {
            web
            //Spring Security ignores request to static resources such as CSS or JS files.
            .ignoring()
            .antMatchers("/static/**");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
            .authorizeRequests() //Authorize Request Configuration
                //the "/" and "/register" path are accepted without login
                //.antMatchers("/", "/register").permitAll()
                //the /acquisition/** need admin role
                //.antMatchers("/acquisition/**").hasRole("ADMIN")
                //.and().exceptionHandling().accessDeniedPage("/Access_Denied");
                //all the path need authentication
                .anyRequest().authenticated()
                .and() //Login Form configuration for all others
            .formLogin()
                .loginPage("/login")
                //important because otherwise it goes in a loop because login page require authentication and authentication require login page
                    .permitAll()
            .and()
            .logout()
                .logoutSuccessUrl("/login?logout")
                .permitAll();
        }
    }
}

Моя среда разработки LDAP, созданная в студии каталогов Apache

ldap

4b9b3361

Ответ 1

Spring Безопасность уже поддерживает LDAP из коробки. На самом деле у него есть целая глава.

Чтобы использовать и настроить LDAP, добавьте зависимость spring-security-ldap, а затем используйте AuthenticationManagerBuilder.ldapAuthentication, чтобы настроить его. LdapAuthenticationProviderConfigurer позволяет вам установить необходимые вещи.

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.ldapAuthentication()
      .contextSource()
        .url(...)
        .port(...)
        .managerDn(...)
        .managerPassword(...)
      .and()
        .passwordEncoder(passwordEncoder())
        .userSearchBase(...)        
        .ldapAuthoritiesPopulator(new UserServiceLdapAuthoritiesPopulater(this.userService));      
}

Что-то вроде этого (оно должно дать вам хотя бы идею о том, что/как настраивать), есть больше опций, но для этого проверьте javadocs. Если вы не можете использовать UserService как и для получения ролей (потому что только роли находятся в базе данных), тогда для этого используйте свой LdapAuthoritiesPopulator.

Ответ 2

Вам необходимо создать CustomAuthenticationProvider, который реализует метод проверки подлинности AuthenticationProvider и переопределить метод аутентификации, например:

@Component
public class CustomAuthenticationProvider
    implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        boolean authenticated = false;
        /**
         * Here implements the LDAP authentication
         * and return authenticated for example
         */
        if (authenticated) {

            String usernameInDB = "";
            /**
             * Here look for username in your database!
             * 
             */
            List<GrantedAuthority> grantedAuths = new ArrayList<>();
            grantedAuths.add(new     SimpleGrantedAuthority("ROLE_USER"));
            Authentication auth = new     UsernamePasswordAuthenticationToken(usernameInDB, password,     grantedAuths);
            return auth;
        } else {
            return null;
        }
    }

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

}

Затем в SecurityConfig вам необходимо переопределить конфигурацию, которая использует AuthenticationManagerBuilder:

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(this.authenticationProvider);
}

Вы можете автоустанавливать CustomAuthenticationProvider:

@Autowired
private CustomAuthenticationProvider authenticationProvider;

Выполняя это, вы можете переопределить поведение проверки подлинности по умолчанию.

Ответ 3

Я также нашел эту главу Spring Docu Custom Authenicator и создайте собственный переключатель между LDAP и мои пользователи БД. Я могу легко переключаться между данными входа с установленными приоритетами (в моем случае выигрыш LDAP).

Я настроил LDAP с файлами конфигурации yaml для пользовательских данных LDAP, которые я не раскрываю здесь подробно. Это можно легко сделать с помощью Spring Конфигурация LDAP для документации.

Я удалил следующий пример с грохота, такого как logger/javadoc и т.д., чтобы выделить важные части. Аннотации @Order определяют приоритеты, в которых используются данные входа. Детали в памяти - это жестко настроенные пользователи отладки для целей только для разработчиков.

SecurityWebConfiguration

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Inject
  private Environment env;
  @Inject
  private LdapConfiguration ldapConfiguration;

  @Inject
  private BaseLdapPathContextSource contextSource;
  @Inject
  private UserDetailsContextMapper userDetailsContextMapper;

  @Inject
  private DBAuthenticationProvider dbLogin;

  @Inject
  @Order(10) // the lowest number wins and is used first
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(new InMemoryUserDetailsManager(getInMemoryUserDetails()));
  }

  @Inject
  @Order(11) // the lowest number wins and is used first
  public void configureLDAP(AuthenticationManagerBuilder auth) throws Exception {
    if (ldapConfiguration.isLdapEnabled()) {
      auth.ldapAuthentication().userSearchBase(ldapConfiguration.getUserSearchBase())
          .userSearchFilter(ldapConfiguration.getUserSearchFilter())
          .groupSearchBase(ldapConfiguration.getGroupSearchBase()).contextSource(contextSource)
          .userDetailsContextMapper(userDetailsContextMapper);
    }
  }

  @Inject
  @Order(12) // the lowest number wins and is used first
  public void configureDB(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(dbLogin);
  }
}

Аутентификатор базы данных

@Component
public class DBAuthenticationProvider implements AuthenticationProvider {

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String name = authentication.getName();
    String password = authentication.getCredentials().toString();

   // your code to compare to your DB
  }

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

  /**
   * @param original <i>mandatory</i> - input to be hashed with SHA256 and HEX encoding
   * @return the hashed input
   */
  private String sha256(String original) {
    MessageDigest md = null;
    try {
      md = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      throw new AuthException("The processing of your password failed. Contact support.");
    }

    if (false == Strings.isNullOrEmpty(original)) {
      md.update(original.getBytes());
    }

    byte[] digest = md.digest();
    return new String(Hex.encodeHexString(digest));
  }

  private class AuthException extends AuthenticationException {
    public AuthException(final String msg) {
      super(msg);
    }
  }
}

Не стесняйтесь спрашивать подробности. Надеюсь, это полезно для кого-то еще: D

Ответ 4

Для любого, кто использует grails, это намного проще. Просто добавьте это в свою конфигурацию:

Grails: плагин:   springsecurity:     LDAP:       власти:          retrieveDatabaseRoles: true

Ответ 5

Ваше ОБНОВЛЕНИЕ С ПРАВИЛЬНЫМ КОДОМ работает отлично. Как я могу получить имя пользователя и фамилию из LDAP после аутентификации?