Spring 安全切换到 Ldap 身份验证和数据库权限
Posted
技术标签:
【中文标题】Spring 安全切换到 Ldap 身份验证和数据库权限【英文标题】:Spring security switch to Ldap authentication and database authorities 【发布时间】: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
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) 用户从网页的登录页面和网络服务的用户名和密码访问。这必须通过 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配置一下。 通过数据库认证?网上有很多例子 【参考方案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 必须非常相似。以上是关于Spring 安全切换到 Ldap 身份验证和数据库权限的主要内容,如果未能解决你的问题,请参考以下文章
通过 spring LDAP 进行身份验证,并在数据库中进行额外的安全检查
Spring Security - 在运行时在身份验证提供者之间切换(本地数据库或远程 LDAP)
Grails - Spring 安全 ldap 活动目录身份验证 - 凭据错误错误