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
Spring security switch to Ldap authentication and database authorities
提问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
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;
}
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 MyAuthoritiesPopulator
class instead of MyUserDetailsService
and 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 开发环境
采纳答案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-ldap
dependency and next use the AuthenticationManagerBuilder.ldapAuthentication
to configure it. The LdapAuthenticationProviderConfigurer
allows 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 UserService
as is to retrieve the roles (because only the roles are in the database) then implement your own LdapAuthoritiesPopulator
for 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 @Order
annotation 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