【问题标题】:Spring security switch to Ldap authentication and database authoritiesSpring 安全切换到 Ldap 身份验证和数据库权限
【发布时间】:2016-04-12 01:02:11
【问题描述】:

我为我的网页和 Web 服务实现了数据库身份验证。 它对两者都有效,现在我必须添加 Ldap 身份验证。 我必须通过远程 Ldap 服务器进行身份验证(使用用户名和密码),如果用户存在,我必须使用我的数据库作为用户角色(在我的数据库中,用户名与 Ldap 的用户名相同)。 所以我必须从我的实际代码切换到如上所述的 Ldap 和数据库身份验证。我的代码是: 安全配置类

@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
        }
    }

MyUserDetailsS​​ervice 类

@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's 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();
        }
    }
}

我在 Apache directory studio 中创建的 LDAP 开发环境

【问题讨论】:

  • 您是否阅读过包含whole chapter on ldap 以及其中包含哪些组件的参考指南。
  • 我读得很快,因为我使用的是注解而不是 xml。我现在又读了一遍
  • 所以答案你也可以用Java配置一下。
  • 通过数据库认证?网上有很多例子

标签: java mysql spring authentication ldap


【解决方案1】:

对于任何使用 grails 的人来说,它要简单得多。只需将其添加到您的配置中:

圣杯: 插入: 弹簧安全: LDAP: 当局: 检索数据库角色:真

【讨论】:

    【解决方案2】:

    我还发现了这一章Spring Docu Custom Authenicator 并在 LDAP 和我的 DB 用户之间构建了我自己的切换。我可以毫不费力地在具有设定优先级的登录数据之间切换(在我的情况下 LDAP 获胜)。

    我已经使用 yaml 配置文件为 LDAP 用户数据配置了一个 LDAP,我在这里不详细披露。这可以通过Spring Docu LDAP Configuration 轻松完成。

    我删除了以下示例,例如 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

    【讨论】:

      【解决方案3】:

      Spring Security 已经支持开箱即用的 LDAP。它实际上有一个whole chapter

      要使用和配置 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

      【讨论】:

      • 我知道 Spring 安全性支持 LDAP,但我必须在 LDAP 和数据库之间进行混合,而且我也找不到仅适用于 LDAP 的有效示例。
      • 您真的阅读过我的回答并阅读过 javadocs 吗?从你没有的评论来看。
      • 是的,它解释说“如果您只想使用 LDAP 进行身份验证,但从不同的源(例如数据库)加载权限,那么您可以提供自己的此接口实现并注入而是那个。”所以我更新了我的第一篇文章
      • 您是否测试了修改后的解决方案?为什么它不是正确的方法(您仍然必须根据您的结构修复您的 ldap 配置)。
      • @M.Deinum 您与我们共享的所有链接都已失效。请检查它们。
      【解决方案4】:

      您需要创建一个 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 /strong>:

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

      您可以这样做自动装配 CustomAuthenticationProvider:

      @Autowired
      private CustomAuthenticationProvider authenticationProvider;
      

      这样做,您可以覆盖默认的身份验证行为。

      【讨论】:

      • 感谢您的回复。对于 LDAP 身份验证,您是指弹簧身份验证还是仅检查用户和密码是否存在?
      • 为什么?为什么这么复杂。 Spring Security 开箱即用支持 Ldap,配置正确。然后,不要从 ldap 检索角色,而是使用数据库驱动 LdapAuthoritiesPopulator(已经有一个用户服务驱动的,所以这一切都可能是一个简单的配置问题)。
      • @luca 使用此实现,您可以覆盖 AuthenticationProvider 的默认行为。我做了类似的事情,因为我有一个第三方应用程序,它为我提供身份验证服务,但与 Ldap 必须非常相似。
      猜你喜欢
      • 2021-02-24
      • 2011-05-04
      • 1970-01-01
      • 2012-12-13
      • 1970-01-01
      • 2012-09-25
      • 2011-10-23
      • 2013-03-15
      • 2012-08-31
      相关资源
      最近更新 更多