首页 文章

Spring安全性切换到Ldap身份验证和数据库权限

提问于
浏览
17

我为我的网页和Web服务实现了数据库身份验证 . 它适用于两者,现在我必须添加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's authorities
        for (UserRole userRole : userRoles) {
            setAuths.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
        }

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

        return Result;
    }

所以我必须要:

1)用户从登录页面访问网页以及用户名和密码进行Web服务 . 这必须通过Ldap来完成 .

2)用户需要数据库查询来验证用户的用户名 . 你知道我怎么能实现这个吗?谢谢

UPDATE WITH RIGHT CODE :关注@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开发环境

ldap

4 回答

  • 0

    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 . 如果您不能按原样使用_1345570来检索角色(因为只有角色在数据库中),那么为此实现自己的LdapAuthoritiesPopulator .

  • 7

    您需要创建一个 CustomAuthenticationProvider wich implements AuthenticationProvideroverride authenticate 方法,例如:

    @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 中,你需要 override the configure 那使用 AuthenticationManagerBuilder

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

    您可以自动装配CustomAuthenticationProvider执行此操作:

    @Autowired
    private CustomAuthenticationProvider authenticationProvider;
    

    执行此操作,您可以覆盖默认的身份验证行为 .

  • 0

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

    我已经为LDAP用户数据配置了一个带有yaml配置文件的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);
      }
    }
    

    DB Authenticator

    @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

  • 1

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

    grails:plugin:springsecurity:ldap:authorities:retrieveDatabaseRoles:true

相关问题