spring-boot项目使用AD认证登录:
package com.test.config;
import java.util.Arrays;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.event.LoggerListener;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;
import com.test.config.KaptchaAuth.KaptchaAuthenticationFilter;
import com.test.handler.MyAuthenctiationSuccessHandler;
@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
@Autowired
private Environment env;
@Autowired
MyLdapUserDetailsMapper myLdapUserDetailsMapper;
@Autowired
private MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;
//定义AD认证方法
@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
final ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(env.getProperty("ldap.domanin"), env.getProperty("ldap.url"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
//provider.setAuthoritiesMapper(myAuthoritiesMapper()); //see http://comdynamics.net/blog/544/spring-security-3-integration-with-active-directory-ldap/
provider.setUseAuthenticationRequestCredentials(true);
//设置角色权限
provider.setUserDetailsContextMapper(myLdapUserDetailsMapper);
return provider;
}
//引入登录监听类(成功/失败),也可以重写这个类。
@Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
//配置地址访问规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").permitAll().anyRequest().fullyAuthenticated()
.and().formLogin()
//.loginPage("/login").defaultSuccessUrl("/").failureUrl("/login").successHandler(myAuthenctiationSuccessHandler)
;
//解决中文乱码问题
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.addFilterBefore(filter,CsrfFilter.class);
//在认证用户名之前认证验证码,如果验证码错误,将不执行用户名和密码的认证
//ChannelProcessingFilter通常是用来过滤哪些请求必须用https协议, 哪些请求必须用http协议, 哪些请求随便用哪个协议都行.
http.addFilterBefore(new KaptchaAuthenticationFilter("/login", "/login"), ChannelProcessingFilter.class);
}
/*
//配置单个AuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//AuthenticationProvider是做验证工作的组件
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}*/
//配置多种认证方式,即多个AuthenticationProvider(用ProviderManager的Arrays.asList添加多个认证方法)
@Override
protected AuthenticationManager authenticationManager() throws Exception {
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
//不擦除认证密码,擦除会导致TokenBasedRememberMeServices因为找不到Credentials再调用UserDetailsService而抛出UsernameNotFoundException
authenticationManager.setEraseCredentialsAfterAuthentication(false);
return authenticationManager;
}
}
设置角色
package com.test.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.stereotype.Component;
import com.test.domain.SysRole;
import com.test.domain.SysUser;
import com.test.repository.SysRoleRepo;
import com.test.repository.SysUserRepo;
/**
* @author zhu
* 用户为登录用户设置角色权限
*/
@Component
public class MyLdapUserDetailsMapper extends LdapUserDetailsMapper {
@Autowired
private SysUserRepo sysUserRepo;
@Autowired
private SysRoleRepo sysRoleRepo;
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
// 根据username读取用户信息,级联查询用户的角色的名称
SysUser sysUser = sysUserRepo.findByUsername(username);
//将角色添加到集合里即可
List<SimpleGrantedAuthority> WhAuthorities = new ArrayList<>();
if(sysUser == null ) {
WhAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}else {
// 新建N个角色
WhAuthorities.add(new SimpleGrantedAuthority(sysUser.getSysrole().getName()));
}
return super.mapUserFromContext(ctx, username, WhAuthorities);
}
}
关键的一步:使用已经登录的信息来配置ldapContextSource ,并配置LdapTemplate BEAN以供其他地方使用
package com.test.config.ldap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource;
@Configuration
public class LdapConfig {
@Value("${ldap.url}")
private String ldapUrl;
@Value("${ldap.baseDn}")
private String ldapBaseDn;
@Value("${ldap.groupSearchBase}")
private String ldapGroupSearchBase;
@Value("${ldap.userDnPattern}")
private String ldapUserDnPattern;
@Value("${ldap.passwordAttribute}")
private String ldapPasswordAttribute;
@Value("${ldap.managerUser}")
private String ldapManagerUser;
@Value("${ldap.managerPassword}")
private String ldapManagerPassword;
@Bean
public LdapContextSource getContextSource() throws Exception{
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl(ldapUrl);
ldapContextSource.setBase(ldapBaseDn);
//ldapContextSource.setAnonymousReadOnly(true);
//使用已经登录的信息来配置ldapContextSource
ldapContextSource.setAuthenticationSource(springSecurityAuthenticationSource());
return ldapContextSource;
}
@Bean
public LdapTemplate ldapTemplate() throws Exception{
LdapTemplate ldapTemplate = new LdapTemplate(getContextSource());
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
}
@Bean
public SpringSecurityAuthenticationSource springSecurityAuthenticationSource() {
return new SpringSecurityAuthenticationSource();
}
}
定义Person类:
package com.test.domain;
import java.security.Timestamp;
import java.util.List;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
/* Entry标记,一定要有objectClasses属性,objectClasses属性要跟AD里的一致,可以少几项,但不能多一项。
* LdapQuery里配置时,不需要再使用.where("objectclass").is("person")指定objectClass
* base属性查询的时候用不到,只有在新建和更新时,ODM会根据此属性进行匹配
*/
@Entry(objectClasses = {"organizationalPerson", "person" }, base="ou=maksad")
public final class Person {
/**
* 每个entry必须指定@Id字段,类型为javax.naming.Name,其实就是DN。
* 但是若在LdapContextSource中指定了base,则DN将会按照base截取相对路径。
* 比如,DN为cn=user,ou=users,dc=test,dc=com,base为dc=test,dc=com,则取出的user对象DN为cn=user,ou=users。
*/
@Id
private Name dn;
/**
* 必填属性,帐户名
*/
private String cn;
/**
* 必填属性,姓
*/
private String sn;
private String whenCreated;
/**
* 类型 //如果字段跟AD里的字段不同,就要用@Attribute来找到对应的AD字段
*/
@Attribute(name = "objectClass")
private List<String> objectclass;
/**
* 电话号码
*/
@Attribute
private String telephoneNumber;
/**
* 跟CN一样
*/
private String sAMAccountName;
/**
* 全称
*/
private String name;
/**
* 邮箱
*/
private String mail;
/**
* 名字
*/
private String givenName;
/**
* 描述
*/
private String description;
/**
* 显示名称
*/
private String displayName;
/**
*
*/
private String distinguishedName;
/**
*
*/
private String userAccountControl;
/**
*
*/
private String userPrincipalName;
/**
* 隶属于
*/
private List<String> memberOf;
/**
* 可选属性
*/
/*@Attribute(name = "userPassword")
private String userPassword; */
/*@Attribute(name="userPassword", type=Type.BINARY)
private byte[] userPassword;*/
public String getDistinguishedName() {
return distinguishedName;
}
public void setDistinguishedName(String distinguishedName) {
this.distinguishedName = distinguishedName;
}
public String getUserPrincipalName() {
return userPrincipalName;
}
public void setUserPrincipalName(String userPrincipalName) {
this.userPrincipalName = userPrincipalName;
}
public String getUserAccountControl() {
return userAccountControl;
}
public void setUserAccountControl(String userAccountControl) {
this.userAccountControl = userAccountControl;
}
public List<String> getObjectclass() {
return objectclass;
}
public void setObjectclass(List<String> objectclass) {
this.objectclass = objectclass;
}
public String getTelephoneNumber() {
return telephoneNumber;
}
public void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn = dn;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getCn() {
return cn;
}
public void setCn(String cn) {
this.cn = cn;
}
public String getsAMAccountName() {
return sAMAccountName;
}
public void setsAMAccountName(String sAMAccountName) {
this.sAMAccountName = sAMAccountName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getGivenName() {
return givenName;
}
public void setGivenName(String givenName) {
this.givenName = givenName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public List<String> getMemberOf() {
return memberOf;
}
public void setMemberOf(List<String> memberOf) {
this.memberOf = memberOf;
}
public String getWhenCreated() {
return whenCreated;
}
public void setWhenCreated(String whenCreated) {
this.whenCreated = whenCreated;
}
@Override
public String toString() {
return "Person [dn=" + dn + ", cn=" + cn + ", sn=" + sn + ", whenCreated=" + whenCreated
+ ", objectclass=" + objectclass + ", telephoneNumber="
+ telephoneNumber + ", sAMAccountName=" + sAMAccountName + ", name=" + name + ", mail=" + mail
+ ", givenName=" + givenName + ", description=" + description + ", displayName=" + displayName
+ ", distinguishedName=" + distinguishedName + ", userAccountControl=" + userAccountControl
+ ", userPrincipalName=" + userPrincipalName + ", memberOf=" + memberOf + "]";
}
}
直接写个REST测试:
package com.test.rest;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import java.util.List;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.test.domain.Person;
import com.test.repository.PersonRepo;
@RestController
@RequestMapping("/admin")
public class LdapController {
private static final Logger LOGGER = LoggerFactory.getLogger(LdapController.class);
@Autowired
private PersonRepo personRepo;
@Autowired
private LdapTemplate ldapTemplate;
@Value("${ldap.searchBaseDn}")
public String BaseDn;
@RequestMapping("/test1")
public String findOne(@RequestParam(value = "username", defaultValue = "test9") String username){
LdapQuery query = query()
.where("cn").is(username);
//.base("ou=maksad")
//.attributes("cn", "sn")
/*.where("objectclass").is("person")
.and("sn").is("朱");*/
Person person = ldapTemplate.findOne(query, Person.class);
System.out.println(person.toString());
return "ok";
}
}