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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34658534/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:53:49  来源:igfitidea点击:

Spring security switch to Ldap authentication and database authorities

javamysqlspringauthenticationldap

提问by luca

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

我为我的网页和 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 class

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

so I have to:

所以我必须要:

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

1)用户从登录页面访问网页和用户名和密码的网络服务。这必须通过 LDAP 来完成。

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

2)用户的用户名需要数据库查询来验证用户。你知道我如何实现这个吗?谢谢

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

使用正确的代码更新:遵循@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;
    }
}

and I changed SecurityConfig as below:

我改变了 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();
        }
    }
}

My LDAP development environment created in Apache directory studio

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

ldap

LDAP

采纳答案by M. Deinum

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

Spring Security 已经支持开箱即用的 LDAP。它实际上有一整章关于这一点。

To use and configure LDAP add the spring-security-ldapdependency and next use the AuthenticationManagerBuilder.ldapAuthenticationto configure it. The LdapAuthenticationProviderConfigurerallows you to set the needed things up.

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

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 UserServiceas is to retrieve the roles (because only the roles are in the database) then implement your own LdapAuthoritiesPopulatorfor that.

类似的东西(它至少应该让你知道什么/如何配置)有更多的选择,但请检查 javadocs 。如果您不能按UserService原样使用来检索角色(因为只有角色在数据库中),请LdapAuthoritiesPopulator为此实现您自己的角色。

回答by melli-182

You need to create a CustomAuthenticationProviderwich implements AuthenticationProvider, and override authenticatemethod, for example:

您需要创建一个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);
    }

}

Then, in your SecurityConfig, you need to override the configurethats use AuthenticationManagerBuilder:

然后,在您的SecurityConfig 中,您需要覆盖使用AuthenticationManagerBuilder的配置

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

You can autowire the CustomAuthenticationProvider doing this:

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

@Autowired
private CustomAuthenticationProvider authenticationProvider;

Doing this, you can override the default authentication behaviour.

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

回答by Dr4gon

I also found this chapter Spring Docu Custom Authenicatorand build my own switch between LDAP and my DB users. I can effortlessy switch between login data with set priorities (in my case LDAPwins).

我还找到了Spring Docu 自定义身份验证器这一章,并在 LDAP 和我的数据库用户之间构建了我自己的切换。我可以在设置优先级的登录数据之间轻松切换(在我的情况下LDAP获胜)。

I have configured an LDAP with the yaml configuration files for the LDAP user data which I don't disclose here in detail. This can be easily done with this Spring Docu LDAP Configuration.

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

I stripped the following example off the clatter such as logger/javadoc etc. to highlight the important parts. The @Orderannotation determines the priorities in which the login data is used. The in memory details are hardcoded debug users for dev only purposes.

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

Feel free to ask details. I hope this is useful for someone else :D

欢迎询问详情。我希望这对其他人有用:D

回答by rhinmass

For anyone using grails it is much simpler. Simply add this to your config:

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

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

grails:插件:springsecurity:ldap:权限:retrieveDatabaseRoles:true