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

2022-01-17 00:00:00 authentication ldap spring mysql java

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

I implemented database authentication for my web page and web service. It work well for both, now I have to add Ldap authentication. I have to authenticate through remote Ldap server (using username and password) and if the user exists I have to use my database for user roles (in my database username is the same username of Ldap). So I have to switch from my actual code to the Ldap and database authentication as above explained. My code is: SecurityConfig class

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

MyUserDetailsService class

@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 完成.

1)access of user from login page for web pages and username and password for web services. This has to be done through Ldap.

2) 数据库查询需要用户的用户名来认证用户.你知道我该如何实现吗?谢谢

2)the username of user needs for database query to authenticate user. Do you have any idea how I can implement this? Thanks

使用正确的代码更新:在@M 之后.Deinum 建议我创建 MyAuthoritiesPopulator 类而不是 MyUserDetailsS​​ervice 并使用数据库和 Ldap 进行身份验证:

UPDATE WITH RIGHT CODE: Following the @M. Deinum advice I create MyAuthoritiesPopulator class instead of MyUserDetailsService and authentication with database and Ldap works:

    @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 更改如下:

and I changed SecurityConfig as below:

@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 开发环境

My LDAP development environment created in Apache directory studio

推荐答案

Spring Security 已经支持开箱即用的 LDAP.它实际上有一个关于此的整章.

Spring Security already supports LDAP out-of-the-box. It actually has a whole chapter on this.

要使用和配置 LDAP,请添加 spring-security-ldap 依赖项,然后使用 AuthenticationManagerBuilder.ldapAuthentication 来配置它.LdapAuthenticationProviderConfigurer 允许您设置所需的东西.

To use and configure LDAP add the spring-security-ldap dependency and next use the AuthenticationManagerBuilder.ldapAuthentication to configure it. The LdapAuthenticationProviderConfigurer allows you to set the needed things up.

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

Something like that (it should give you at least an idea on what/how to configure things) there are more options but check the javadocs for that. If you cannot use the UserService as is to retrieve the roles (because only the roles are in the database) then implement your own LdapAuthoritiesPopulator for that.

相关文章