SpringBoot + Spring Security + LDAP login without UID - spring-boot

I am new to LDAP, and I am trying to figure out how to use spring security with ldap for login. All the examples/guides/tutorials that I read, are using the UID as a log in form. I have no UID.
This is the structure:
LDAP structure
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userDnPatterns("mail={0},ou=group")
.groupSearchBase("ou=group")
.contextSource()
.url("ldap://localhost:8389/dc=ad,dc=company,dc=com")
.and()
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
Here I tried to replace the UID with MAIL , but it does not work.
error:
Reason: Uncategorized exception occured during LDAP processing; nested exception is javax.naming.NamingException: [LDAP: error code 1 - 000004DC: LdapErr: DSID-0C0907C2, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v2580]; remaining name 'mail=erik.zs#mail.com, '
I hope someone knows how to add login without UID, thank you in advance.

Putting it into an answer, since it involves code blocks now, and these don't work well with the comments.
As discussed above in comments, your problem is that Spring uses LdapTemplate for LDAP access, which uses LDAP search for all authentication calls. Searching LDAP usually requires an admin account, called managerDn by the Spring LDAP classes. This managerDN is usually set when creating the LDAP context source. In your case, the methods to set it are provided by the contextSource() builder, to be set as follows:
auth.ldapAuthentication()
.userDnPatterns("mail={0},ou=group")
.groupSearchBase("ou=group")
.contextSource()
.managerDN("your admin account's Distinguished Name (DN)")
.managerPassword("password associated with your admin account")
.url("ldap://localhost:8389/dc=ad,dc=company,dc=com")
.and()
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");

Related

hasRole() and denyAll() method don't restrict access to resources

I'm developing Spring Boot and Spring Security web application with authorization and resource servers enabled. I have defined a set of users with roles assigned to them and trying to implement roles based access to REST endpoints. I was able to implement token based access to endpoints, but can't restrict access to end users, that would be based on their roles.
I have done two endpoints: /rest/products/list and /rest/products/add and trying to restrict access to /rest/products/add endpoint with the user that is of ADMIN role.
My WebSecurityConfigurerAdapter is as follows:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user1")
.password(passwordEncoder.encode("user1Pass"))
.roles("USER")
.and()
.withUser("user2")
.password(passwordEncoder.encode("user2Pass"))
.roles("USER")
.and()
.withUser("admin")
.password(passwordEncoder.encode("adminPass"))
.roles("ADMIN");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/rest/products/add").hasAnyRole("ADMIN")
.antMatchers("/rest/products/list").denyAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Therefore, resource /rest/products/add should be accessible to admin / adminPass user only as far as that user has ADMIN role. But if to try to it with user1 / user1Pass, it is still accessible:
Get access token for user1 postman screen
Accessing ADMIN only related endpoint with user1 Postman screen
Also I added (in the testing purpose) in the configuration method the following rule .antMatchers("/products/list").denyAll(); Here is indicated that /products/list shouldn't be accessible to any user. But it still keeps on responding (provided access correct token).
In the similar question here How to fix role in Spring Security? the order of matchers should be from the more specific to the less. But in my case there are two matchers and no matchers that can overlap them.
I'm using Spring Boot with spring-boot-starter-security plugin version 2.5.2.
What additional configuration should be done to make .hasRole("ADMIN") and .denyAll() work as expected?
Finally was able to find the solution with the following:
Here there is an example of ResourceServerConfigurerAdapter class. From this and from your comment, dur, I realized that I confused ResourceServerConfigurerAdapter and WebSecurityConfigurerAdapter, trying to define access restriction matchers in WebSecurityConfigurerAdapter. I changed resource server configuration in the following way:
Method that was in WebSecurityConfigurerAdapter
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/rest/products/add").hasAnyRole("ADMIN")
.antMatchers("/rest/products/list").denyAll();
}
was moved to
#Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/rest/products/add").hasAnyRole("ADMIN")
.antMatchers("/rest/products/list").denyAll();
}
}
Now restrictions defined by matchers above are working as expected.

Spring Security that needs username and password on every request

What I wanted to do is build a Rest backend application that needs the Authorization header on every request, validate and return the data or 401 Unauthorized if the user or password is wrong.
I have Spring Security in my classpath with this simple configuration:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("user")
.password("pass")
.roles("USER");
}
}
But one thing is not working correctly: when I make a request with valid username and password with Postman, the server responds the data correctly, but if I change the password to a wrong password and keep the correct username, the server stills responds with the data and OK status, as if it is using a JSESSIONID Cookie to check the further requests.
Is that a way of using spring security only for checking the header Authorization (with no cookies, sessions nor saving user informations or login and logout pages), or is better to just use Filters for doing that instead?
Thank you for any help !
Just add the following to your configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
This will configure spring security to never create a cookie, every request must reauthenticate.

Authentication with Spring-Security via Active Directory LDAP

I can't authenticate using a real active directory, let me explain better I tried to authenticate using the example proposed by spring.io without problem where a internal service is started without any problem.
reference https://spring.io/guides/gs/authenticating-ldap/
I tried to modify the code below by inserting the configuration of my active directory without success. Can you kindly guide me or show me a real case where a true connection is made without using internal services like those in the examples? I looked on the net but found everything similar to the official example without any real case
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
Error show:
Uncategorized exception occured during LDAP processing; nested exception is javax.naming.NamingException: [LDAP: error code 1 - 000004DC: LdapErr: DSID-0C0907C2, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v2580
Yeah, authentication via LDAP that's too painful. In order to be able to perform authentication to AD you need to use the ActiveDirectoryLdapAuthenticationProvider.
Here is the working sample:
#Override
protected void configure(AuthenticationManagerBuilder auth) {
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://localhost:8389");
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(adProvider);
}
And to save your time just read the following, that's really important:
AD authentication doc
I found a sample over here, which was useful:
https://github.com/sachin-awati/Mojito/tree/master/webapp/src/main/java/com/box/l10n/mojito/security
You can optionally implement UserDetailsContextMapperImpl which overrides mapUserFromContext to create the UserDetails object if the user is not found during the Active Directory lookup - loadUserByUsername.
#Component
public class UserDetailsContextMapperImpl implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations dirContextOperations, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails userDetails = null;
try {
userDetails = userDetailsServiceImpl.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
String givenName = dirContextOperations.getStringAttribute("givenname");
String surname = dirContextOperations.getStringAttribute("sn");
String commonName = dirContextOperations.getStringAttribute("cn");
userDetails = userDetailsServiceImpl.createBasicUser(username, givenName, surname, commonName);
}
return userDetails;
}
Ensure you are using the ActiveDirectoryLdapAuthenticationProvider spring security class as Active Directory has its own nuances compared to other LDAP servers. You'll probably need to be using the #EnableGlobalAuthentication annotation in your security configuration class as you can have multiple AuthenticationManagerBuilders which confuses things a lot.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://primarydc.domain.com:389");
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(adProvider);
}
More details here:
https://github.com/spring-projects/spring-security/issues/4324
https://github.com/spring-projects/spring-security/issues/4571

LDAP authentication with AD LDP from Spring Boot application

I am trying to implement LDAP authentication in a Sprint Boot application. In the test environment I have installed an Active Directory LDP service with which to authenticate. I have created a user within the AD instance, enabled the account and set a password. I am then trying to authenticate using this account from the Spring login form.
When I try to log in using AD I get an error message:
Your login attempt was not successful, try again.
Reason: Bad credentials
As I am new to both AD and Spring it is quite possible I have mis-configured either (or both!).
Do you have any suggestions as to how I can further diagnose this problem or is there anything obvious I may have missed?
My Spring Boot code (I have tried a number of different variations on this code, this is one example):
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider =
new ActiveDirectoryLdapAuthenticationProvider("foo.bar", "ldap://servername:389");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
It turns out that there was nothing wrong with my Java implementation. The issue appears to be with the AD LDP configuration. I tried connecting to another, known good instance of AD LDP and authentication worked first time.
I am going to mark this as the answer as I am no longer interested in a solution to this question and wish to close it down...

Spring Security / rolesAllowed / antMatchers

I am using Spring Data REST and I have a find method in my repository:
public List<Contact> findByLastNameOrderByLastNameAsc(#Param("lastName") String lastName);
I am trying to add security to the method, but no luck. In my DB, I have 1 user with the role 'ROLE_USER'. When the service starts, the login form comes up and I am able to login with the credentials in the DB.
Here is my web security config:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username,identification,enabled from users where username = ?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/contacts/findByLastNameOrderByLastNameAsc").hasRole("ADMIN")
.antMatchers("/contacts/**").fullyAuthenticated()
.antMatchers("/contacts/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin();
}
When I try to invoke the service in my repository, I am not seeing any authentication error. Using my browser, the URL comes up fine, even though the user in the DB does not have the 'ADMIN' role.
I tried adding 'RolesAllowed' to the method in my repository, but no luck:
#RolesAllowed(value = { "ADMIN" })
public List<Contact> findByLastNameOrderByLastNameAsc(#Param("lastName") String lastName);
Am I going about adding security to the REST API provided by Spring Data correctly? Ideas on how to get this to work?
thanks
FWIW: I forgot to add the jsr250 support. So, I added this config:
#Configuration
#EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
}
Now the RolesAllowed annotation is working fine.

Resources