I know almost nothing about LDAP and even less about spring security but I am trying to configure a spring boot app to authenticate against an ldap instance and am stuck.
I was given the ldap server name at adldap.company.com and base dn of dc=ad,dc=company,dc=com
I have some python code that does a simple bind and works.
LDAP_USERNAME = 'username#ad.company.com'
LDAP_PASSWORD = 'password'
base_dn = 'dc=ad,dc=company,dc=com' # not used for bind I guess, only search
try:
ldap_client = ldap.initialize('ldap://adldap.company.com')
ldap_client.set_option(ldap.OPT_REFERRALS,0)
ldap_client.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD)
except ldap.INVALID_CREDENTIALS as e:
ldap_client.unbind()
return 'Wrong username and password: %s' % e
except ldap.SERVER_DOWN:
return 'AD server not available'
If I run this code, it seems to successfully bind as "username#ad.company.com" with password "password".
I also have a WebSecurityConfig class that I think should be handling auth:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secure")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0}")
.contextSource()
.url("ldap://adldap.company.com");
//.url("ldap://adldap.company.com/dc=ad,dc=company,dc=com");
}
}
When I go to /secure in the app, I get a basic auth pop up but then anything I try entering gets me a 401 Unauthorized. I have tried "username#ad.company.com", without the domain, putting that stuff in the userDnPatterns like {0}#adldap.company.com and a bunch of other things. I have tried using different URLs with the base dn in it or not. Nothing seems to work. What am I missing?
Also, is this the right way to auth users? I've read about both bind authentication and something about binding and searching but the server doesn't allow anonyous binds so I guess I would need some kind of "app user" that could bind and do the searches, right? Is that "better"?
Active Directory has its own non-standard syntax for user authentication, different from the usual LDAP DN binding.
Spring Security provides a specialized AuthenticationProvider for Active Directory.
Try this :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secure")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#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("adldap.company.com", "ldap://adldap.company.com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
Long story short, the problem is that Microsoft Active Directory LDAP is not "Vanilla" LDAP and thus you need to connect to it differently.
The working solution is here: https://medium.com/#dmarko484/spring-boot-active-directory-authentication-5ea04969f220
Related
I want to add simple config for basic authentication using spring security InMemoryUserDetailsManager
After adding following configuration I am able to authenticate with the in memory user (myUser) and the password for this user:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager());
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
List<UserDetails> userDetailsList = new ArrayList<>();
userDetailsList.add(User.withUsername("myUser").password(passwordEncoder().encode("password"))
.roles("USER").build());
return new InMemoryUserDetailsManager(userDetailsList);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The thing is that if I change the password from postman I am still able to authenticate. If I stop application server and start the application again and try with wrong password and correct username it returns 401 ( which is expected). However if next request is sent with the correct header with username and password (myUser, password) and then send the request after that with wrong password it seems the wrong password is accepted. As soon as I change the username to some random word it returns 401 unauthorized. Something is missing from my configuration and I do not have a clue what is it.
Spring by default stores the HttpSession of the Authentication details. So whenever user logs in and authentication is successful, the details are stores in ThreadLocal and whenever the next login happens, it picks it up from the security context instead of authenticating again. Spring Security provides multiple Policies for Session Management. For your use case, you need to configure your HttpSecurity with SessionCreationPolicy.STATELESS.
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You can also refer the below article for detailed information:
https://www.javadevjournal.com/spring-security/spring-security-session/
EDIT:
I further drilled down the problem and turns out issue persists even with single configuration. If I use single configuration and keep
http.antMatcher("/api/test/**")
urls don't get secured.
Removing the antMatcher and antMatchers immediately secures the url.
i.e if I use:
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
then only spring security is securing url. Why isn't antMatcher functioning?
(Updated the title to include actual issue.)
Original Post:
I have referred following stackoverflow questions:
Spring REST security - Secure different URLs differently
Using multiple WebSecurityConfigurerAdapter with different AuthenticationProviders (basic auth for API and LDAP for web app)
and spring security doc:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#multiple-httpsecurity
But I am not able to configure multiple http security elements.
When I follow the official spring doc, it works in my case only becuase of the fact that the second http security element is a catch-all, but as soon as I add a specific url, all the urls can be accessed without any authentication.
Here's my code:
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
Now any url can be accessed. If I remove antMatcher from second configuration, all the urls become secured.
The pattern must not contain the context path, see AntPathRequestMatcher:
Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest.
and HttpServletRequest.html#getServletPath:
Returns the part of this request's URL that calls the servlet. This path starts with a "/" character and includes either the servlet name or a path to the servlet, but does not include any extra path information or a query string. Same as the value of the CGI variable SCRIPT_NAME.
and HttpServletRequest.html#getContextPath:
Returns the portion of the request URI that indicates the context of the request. The context path always comes first in a request URI. The path starts with a "/" character but does not end with a "/" character. For servlets in the default (root) context, this method returns "". The container does not decode this string.
Your modified and simplified code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/test/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
Google chrome [EDIT] Version 80.0.3987.162 (Official Build) (64-bit)[/EDIT] waits for a response indefinitely when trying to login to my spring security web app.
I managed to produce this error with a simple demo app using spring initialzr with these specs:
spring boot 2.2.6
spring web
spring boot devtools
spring security
The only thing I added, was a simple WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService userDetailsService;
#Autowired
public WebSecurityConfig(final MyUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout().permitAll();
}
#Autowired
public void globalSecurityConfiguration(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth
.userDetailsService(userDetailsService)
.passwordEncoder(encoder);
}
}
and a UserDetailsService
#Service
public class MyUserDetailsService implements UserDetailsService {
// #Autowired
// private UserRepository userRepository;
public MyUserDetailsService() {
}
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
return getUserTestStyle();
}
private UserDetails getUserTestStyle() {
return new MyUser("admin", "SimplePassword#");
}
}
I see that in devtools, the login-POST-Request stays on Pending, even after the server has fully processed the login-request (and confirms in DEBUG-log that authentication was successful).
Things I observed and confirmed:
the server authentication is successful (even though the browser does not redirect) (i can access secured URLs as soon as the server processed the login-request)
this happens only on the very first login-attempt when Chrome has been freshly opened (but when doing so, the issue happens every time)
the problem does not occur, when I change the password on the server side, and enter the changed password on the client
the problem has been gone after I completely cleared all my browser data (CTRL+SHIFT+DEL)
this problem is chrome-specific, I cannot reproduce this with Firefox or Microsoft Edge
I'd like to prevent people from accessing my application (Angular 7 frontend, Spring Boot backend) when they are not logged in using Spring-Boot-Security. I authenticate my user via ldap and would like to set roles based on database entries, but let's get to this step by step. So what I did now is replacing the default Spring-Boot-Security-login (via the generated password) with my ldap-configuration (but still using the default login-page). I got the following code for that:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/login")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
http.formLogin().defaultSuccessUrl("/", true);
}
#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(AD_DOMAIN, AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Now by default when including the spring-boot-starter-security artifact, everyone is redirected to the /login-page when trying to access any page. Sadly since I overwrote that config with my own that is not the case any more. How can I let spring do this again (also with the frontend-pages, which were prevented from accessing too)?
I am getting a 404 after I loggin in a very simple Spring Boot Application. It happen's since I added the password encoder stuff into my configureAuth method. Can someone help me?
Here ist my security configuration code:
#Configuration
#EnableGlobalAuthentication
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureAuth(final AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().passwordEncoder(passwordEncoder()).dataSource(dataSource).withDefaultSchema()
.withUser("admin").password(passwordEncoder().encode("admin123")).roles("USER", "ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic().and().csrf().disable()
.headers().frameOptions().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
There is no exception or other error. A simple whitelabel error page with 404 is showing up.
EDIT: The login form is coming up, but I think there is something wrong with the authentication.
Thank you,
Christian
You have to configure requests to the login form I believe. Reference.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login");
}
From what it looks like, it's important to specify the .loginPage. I'm using the following config for my project.
http.
.authorizeRequests().antMathcers("/login-page", "/login", "/successful-login", "/error-login").anonymous().and()
.formLogin()
.loginPage("/login-page")
.defaultSuccessUrl("/successful-login")
.loginProcessingUrl("/login")
.failureUrl("/error-login")
.permitAll()
The .loginProcessingUrl is I believe the URL to handle the login POST request.
I'm also using the #EnableWebSecurity annotation on my SecurityConfig class,
My case...
It worked properly
antMatchers("/admin/**")
It was failed after I changed like this
antMatchers("/admin/xxx", "/admin/yyyy", "/admin/zzz")
Solution is to add the loginProc URL like this
antMatchers("/admin/xxx", "/admin/yyyy", "/admin/zzz", "/admin/loginProc")