Spring 安全 LDAP 身份验证并从本地数据库收集用户详细信息
Spring Security LDAP Authentication and gather user details from local database
总而言之,正在对用户进行身份验证,但我似乎确实已登录到用户帐户。
我目前正在为一个项目实施 LDAP 身份验证。从我的应用程序确实接受正确凭据的意义上来说,事情的身份验证部分似乎在起作用。我遇到的问题是我似乎无法在 jsp 视图中访问 'principal'。 (在切换到 LDAP 之前,我能够访问所有这些)。当 运行 跟踪时,我的 CustomUserDetails 服务正在查询并提取正确的帐户信息。感谢任何帮助
这将显示正确的用户名:
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="name"/></h2>
</sec:authorize>
这不行(它在 LDAP 之前工作)
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="principal.firstName"/></h2>
</sec:authorize>
相关代码
SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public CustomSaltSource customSaltSource(){ return new CustomSaltSource();}
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new AuthenticationSuccessHandler();
}
@Autowired
void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().contextSource()
.url("ldap://bar.foo.com")
.port(####)
.and()
.userDnPatterns("cn={0},cn=users,dc=ms,dc=ds,dc=foo,dc=com")
.ldapAuthoritiesPopulator(new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/skins/**", "/css/**", "/**/laggingComponents", "/assets/**").permitAll().and()
.formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/", true).successHandler(myAuthenticationSuccessHandler())
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).deleteCookies("JSESSIONID").permitAll()
.and().authorizeRequests().antMatchers("/api/**").anonymous()
.and().authorizeRequests().anyRequest().authenticated().and().rememberMe().key("KEY").userDetailsService(userDetailsService);
}
@Override
public void configure(WebSecurity web) throws Exception {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new PermissionEvaluator());
web.expressionHandler(handler);
web.ignoring().antMatchers( "/skins/**", "/css/**", "/api/**", "/assets/**", "/health"); //"/**/test/**"
}
}
CustomUserDetaulsService.java
import org.hibernate.Session;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class CustomUserDetailsService implements UserDetailsService{
@Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
Session session = DBFactory.factory.openSession();
User user = (User) session.createQuery("from User where userName =:userName")
.setParameter("userName", username).uniqueResult();
if(user == null){
throw new UsernameNotFoundException("User Not Found");
}
//Needed to initialize permissions
Set<Role> roles = user.getRoles();
int i = roles.size();
for(Role role: roles){
int j = role.getPermissions().size();
}
CustomUserDetails userDetails = new CustomUserDetails(user);
session.close();
return userDetails;
}
}
如果我没记错的话
您切换到 Ldap 授权,设置 url 和 DN 模式,但仍然提供在数据库中搜索用户的 userDetailsService。
您需要通过实现接口并创建自定义接口来设置 UserDetailsContextMapper。这将通过 mapUserFromContext 方法将数据从 ldap 目录上下文映射到您的自定义 UserDetails 和 return。
这是一个例子CustomUserDetailsContextMapper:
public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
private LdapUser ldapUser = null;
private String commonName;
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
} catch (NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser(ldapUserDetails);
ldapUser.setCommonName(commonName);
return ldapUser;
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
我的自定义 LdapUser:
public class LdapUser implements UserDetails
{
private String commonName;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
@Override
public String getDn() {
return ldapUserDetails.getDn();
}
@Override
public void eraseCredentials() {
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
@Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
@Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
}
然后在授权配置中设置CustomUserDetailsContextMapper。这就是您能够从 authentication.getPrincipal() 获取用户的方式。
我希望我正确理解你的问题并回答。
我必须测试 LDAP 身份验证 + 开发人员、管理员、用户的不同角色
非常感谢@Yernar Arystanov
代码不是很干净但是可以工作...
public class LdapUser implements UserDetails
{
private String commonName;
private List<String> groups;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
@Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
@Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
public String getCommonName() {
return commonName;
}
public void setCommonName(String commonName) {
this.commonName = commonName;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
}
@Ivan Baranuk 的建议是正确的(扩展 LdapUserDetailsMapper):
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper {
private LdapUser ldapUser = null;
private String commonName;
private List<String> groups = new LinkedList<String>();
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails;
ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
Arrays.stream(ctx.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
groups.add(m.toString());
});
} catch (NamingException | javax.naming.NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser((LdapUserDetails) ldapUserDetails);
ldapUser.setCommonName(commonName);
ldapUser.setGroups(groups);
return ldapUser;
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
然后最后在 SecurityConfig 中:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
public void configure(AuthenticationManagerBuilder auth) throws Exception {
...
auth.ldapAuthentication()
.userSearchBase("enter your search base")
.userSearchFilter("(&(objectClass=user)(sAMAccountName={0}))")
.userDetailsContextMapper(ldapUserDetailsMapper())
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator())
.contextSource()
.url(yourProperties.getLdapUrl())
.managerDn(yourProperties.getManagerDn())
.managerPassword(yourProperties.getManagerPassword());
}
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
return new LdapAuthoritiesPopulator() {
@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData,
String username) {
LinkedList<SimpleGrantedAuthority> res = new LinkedList();
Arrays.stream(userData.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
if(m.toString().equals("your_dev_group"))
res.add(new SimpleGrantedAuthority("DEV_USER"));
if(m.toString().equals("your_admin_group"))
res.add(new SimpleGrantedAuthority("ADMIN_USER"));
});
if(res.isEmpty())
return Arrays.asList(new SimpleGrantedAuthority("USER"));
else
return res;
}
};
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Bean
protected LdapUserDetailsMapper ldapUserDetailsMapper() {return new CustomUserDetailsContextMapper();}
}
最后 - 一些简单的获取方法(仅限授权用户):
@RequestMapping("/logger")
public String testLogger(Authentication authentication) {
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_DEV_USER"))) {
LdapUser userDetails = (LdapUser) authentication.getPrincipal();
log.info("WELCOME :" + userDetails.getCommonName());
userDetails.getGroups().iterator().forEachRemaining((g) - > log.info("group: " + g.toString()));
}
return ""
}
总而言之,正在对用户进行身份验证,但我似乎确实已登录到用户帐户。
我目前正在为一个项目实施 LDAP 身份验证。从我的应用程序确实接受正确凭据的意义上来说,事情的身份验证部分似乎在起作用。我遇到的问题是我似乎无法在 jsp 视图中访问 'principal'。 (在切换到 LDAP 之前,我能够访问所有这些)。当 运行 跟踪时,我的 CustomUserDetails 服务正在查询并提取正确的帐户信息。感谢任何帮助
这将显示正确的用户名:
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="name"/></h2>
</sec:authorize>
这不行(它在 LDAP 之前工作)
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="principal.firstName"/></h2>
</sec:authorize>
相关代码 SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public CustomSaltSource customSaltSource(){ return new CustomSaltSource();}
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new AuthenticationSuccessHandler();
}
@Autowired
void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().contextSource()
.url("ldap://bar.foo.com")
.port(####)
.and()
.userDnPatterns("cn={0},cn=users,dc=ms,dc=ds,dc=foo,dc=com")
.ldapAuthoritiesPopulator(new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/skins/**", "/css/**", "/**/laggingComponents", "/assets/**").permitAll().and()
.formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/", true).successHandler(myAuthenticationSuccessHandler())
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).deleteCookies("JSESSIONID").permitAll()
.and().authorizeRequests().antMatchers("/api/**").anonymous()
.and().authorizeRequests().anyRequest().authenticated().and().rememberMe().key("KEY").userDetailsService(userDetailsService);
}
@Override
public void configure(WebSecurity web) throws Exception {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new PermissionEvaluator());
web.expressionHandler(handler);
web.ignoring().antMatchers( "/skins/**", "/css/**", "/api/**", "/assets/**", "/health"); //"/**/test/**"
}
}
CustomUserDetaulsService.java
import org.hibernate.Session;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class CustomUserDetailsService implements UserDetailsService{
@Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
Session session = DBFactory.factory.openSession();
User user = (User) session.createQuery("from User where userName =:userName")
.setParameter("userName", username).uniqueResult();
if(user == null){
throw new UsernameNotFoundException("User Not Found");
}
//Needed to initialize permissions
Set<Role> roles = user.getRoles();
int i = roles.size();
for(Role role: roles){
int j = role.getPermissions().size();
}
CustomUserDetails userDetails = new CustomUserDetails(user);
session.close();
return userDetails;
}
}
如果我没记错的话 您切换到 Ldap 授权,设置 url 和 DN 模式,但仍然提供在数据库中搜索用户的 userDetailsService。 您需要通过实现接口并创建自定义接口来设置 UserDetailsContextMapper。这将通过 mapUserFromContext 方法将数据从 ldap 目录上下文映射到您的自定义 UserDetails 和 return。
这是一个例子CustomUserDetailsContextMapper:
public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
private LdapUser ldapUser = null;
private String commonName;
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
} catch (NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser(ldapUserDetails);
ldapUser.setCommonName(commonName);
return ldapUser;
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
我的自定义 LdapUser:
public class LdapUser implements UserDetails
{
private String commonName;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
@Override
public String getDn() {
return ldapUserDetails.getDn();
}
@Override
public void eraseCredentials() {
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
@Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
@Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
}
然后在授权配置中设置CustomUserDetailsContextMapper。这就是您能够从 authentication.getPrincipal() 获取用户的方式。 我希望我正确理解你的问题并回答。
我必须测试 LDAP 身份验证 + 开发人员、管理员、用户的不同角色
非常感谢@Yernar Arystanov
代码不是很干净但是可以工作...
public class LdapUser implements UserDetails
{
private String commonName;
private List<String> groups;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
@Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
@Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
public String getCommonName() {
return commonName;
}
public void setCommonName(String commonName) {
this.commonName = commonName;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
}
@Ivan Baranuk 的建议是正确的(扩展 LdapUserDetailsMapper):
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper {
private LdapUser ldapUser = null;
private String commonName;
private List<String> groups = new LinkedList<String>();
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails;
ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
Arrays.stream(ctx.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
groups.add(m.toString());
});
} catch (NamingException | javax.naming.NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser((LdapUserDetails) ldapUserDetails);
ldapUser.setCommonName(commonName);
ldapUser.setGroups(groups);
return ldapUser;
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
然后最后在 SecurityConfig 中:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
public void configure(AuthenticationManagerBuilder auth) throws Exception {
...
auth.ldapAuthentication()
.userSearchBase("enter your search base")
.userSearchFilter("(&(objectClass=user)(sAMAccountName={0}))")
.userDetailsContextMapper(ldapUserDetailsMapper())
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator())
.contextSource()
.url(yourProperties.getLdapUrl())
.managerDn(yourProperties.getManagerDn())
.managerPassword(yourProperties.getManagerPassword());
}
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
return new LdapAuthoritiesPopulator() {
@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData,
String username) {
LinkedList<SimpleGrantedAuthority> res = new LinkedList();
Arrays.stream(userData.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
if(m.toString().equals("your_dev_group"))
res.add(new SimpleGrantedAuthority("DEV_USER"));
if(m.toString().equals("your_admin_group"))
res.add(new SimpleGrantedAuthority("ADMIN_USER"));
});
if(res.isEmpty())
return Arrays.asList(new SimpleGrantedAuthority("USER"));
else
return res;
}
};
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Bean
protected LdapUserDetailsMapper ldapUserDetailsMapper() {return new CustomUserDetailsContextMapper();}
}
最后 - 一些简单的获取方法(仅限授权用户):
@RequestMapping("/logger")
public String testLogger(Authentication authentication) {
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_DEV_USER"))) {
LdapUser userDetails = (LdapUser) authentication.getPrincipal();
log.info("WELCOME :" + userDetails.getCommonName());
userDetails.getGroups().iterator().forEachRemaining((g) - > log.info("group: " + g.toString()));
}
return ""
}