How to Connect to an LDAP Server and how to validate AD Groups in a Spring Boot Application - spring

I have a Spring Boot application.
I want to connect to an LDAP server and query for valid AD groups.

I hope you are familiar with Spring Security. Write a configuration class which extends WebSecurityConfigurerAdapter and configure AD authentication provider. Refer below code. Change antMatchers to match your app.
Add dependencies spring-security-ldap,spring-boot-starter-security as well.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers(AppConstants.LOGIN_URI).fullyAuthenticated().and().formLogin().permitAll();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
String ldapUrl = env.getProperty(AppConstants.LDAP_URL);
String ldapDomain = env.getProperty(AppConstants.LDAP_DOMAIN);
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapDomain,
ldapUrl);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(provider);
}
To get the groups to which user is assigned inject instance of Authentication/Principal in controller class. Get
To get all attributes against the user like email, manager etc. you may will need to write a custom mapper though. Refer code below.
#GetMapping(value = { AppConstants.REST_API_LOGIN })
public ResponseEntity<User> authenticateUser(Authentication auth) {
List<String> ldapRoles = new ArrayList<String>();
auth.getAuthorities().forEach(a -> ldapRoles.add(a.getAuthority()));
/*
Write service class methods to compare ldapRoles against app specific roles from DB and exception handlers to handle exceptions
*/
User user = userService.getUser(auth);
if (!user.isAuthorized()) {
logger.info("User:" + auth.getName() + " is not authorized to access program.");
throw new UserAuthenticationException(AppConstants.NOT_VALID_MEMBER, user);
}
logger.info("User:" + auth.getName() + " logged in");
return new ResponseEntity<User>(user, HttpStatus.OK);
}

Related

Spring Security: Multiple OpenID Connect Clients for Different Paths?

Using Spring Boot 2.1.5 and Spring Security 5, I'm trying to use two different OpenID clients (based in Keycloak). Here is what we have in application.properties.
spring.security.oauth2.client.registration.keycloak-endusersclient.client-id=endusersclient
spring.security.oauth2.client.registration.keycloak-endusersclient.client-secret=7b41aaa4-277f-47cf-9eab-91afacd55d2c
spring.security.oauth2.client.provider.keycloak-endusersclient.issuer-uri=https://mydomain/auth/realms/endusersrealm
spring.security.oauth2.client.registration.keycloak-employeesclient.client-id=employeesclient
spring.security.oauth2.client.registration.keycloak-employeesclient.client-secret=7b41aaa4-277f-47cf-9eab-91afacd55d2d
spring.security.oauth2.client.provider.keycloak-employeesclient.issuer-uri=https://mydomain/auth/realms/employeesrealm
You can see from the snippet above, we are trying to use one OpenID client for endusers (customers) and another for employees.
In the security configuration class, we see how to configure security on different patterns as follows:
public class OpenIDConnectSecurityConfig extends
WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
// avoid multiple concurrent sessions
http.sessionManagement().maximumSessions(1);
http.authorizeRequests()
.antMatchers("/endusers/**").authenticated()
.antMatchers("/employees/**").authenticated()
.anyRequest().permitAll().and()
.oauth2Login()
.successHandler(new OpenIDConnectAuthenticationSuccessHandler())
.and()
.logout().logoutSuccessUrl("/");
What I don't understand is how to configure each OpenID client to fire on a separate URL pattern. In the example above, we would like to see the endusers client be used when hitting URL's starting with "/endusers", and to use the employees client when hitting URL's starting with "/employees".
Can this be done?
You need to use AuthenticationManagerResolver for the multi-tenant case, in which endusersclient and employeesclient are your tenants.
public class CustomAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {
#Override
public AuthenticationManager resolve(HttpServletRequest request) {
return fromTenant();
}
private AuthenticationManager fromTenant(HttpServletRequest request) {
String[] pathParts = request.getRequestURI().split("/");
//TODO find your tanent from the path and return the auth manager
}
// And in your class, it should be like below
private CustomAuthenticationManagerResolver customAuthenticationManagerResolver;
http.authorizeRequests()
.antMatchers("/endusers/**").authenticated()
.antMatchers("/employees/**").authenticated()
.anyRequest().permitAll().and().oauth2ResourceServer().authenticationManagerResolver(this.customAuthenticationManagerResolver);
For Opaque Token (Multitenant Configuration)
#Component
public class CustomAuthenticationManagerResolver implements AuthenticationManagerResolver {
#Override
public AuthenticationManager resolve(HttpServletRequest request) {
String tenantId = request.getHeader("tenant");
OpaqueTokenIntrospector opaqueTokenIntrospector;
if (tenantId.equals("1")) {
opaqueTokenIntrospector = new NimbusOpaqueTokenIntrospector(
"https://test/authorize/oauth2/introspect",
"test",
"test"
);
} else {
opaqueTokenIntrospector = new NimbusOpaqueTokenIntrospector(
"https://test/authorize/oauth2/introspect",
"test",
"test");
}
return new OpaqueTokenAuthenticationProvider(opaqueTokenIntrospector)::authenticate;
}
}
Web Security Configuration
#Autowired
private CustomAuthenticationManagerResolver customAuthenticationManagerResolver;
#Override
public void configure(HttpSecurity http) throws Exception {
http.anyRequest()
.authenticated().and().oauth2ResourceServer()
.authenticationEntryPoint(restEntryPoint).authenticationManagerResolver(customAuthenticationManagerResolver);
}

Can not retrieve Principal from Spring Okta (CRA SPA)

I am currently trying to test out Okta with SPA front end (Create-React-App) and a Spring Boot application.
Currently I have the apps working, in that a user logins on the front end (via okta). The user can then access protected resources from server (spring boot). Hence the integration works well and nice.
My issue is I can't access the Principal on my Rest Controller.
ENV
Note: Spring-Security-Starter is NOT on the classpath just the OAuth2 autoconf
Spring Boot 2.0.6.RELEASE
okta-spring-boot-starter:0.6.1
spring-security-oauth2-autoconfigure:2.0.6.RELEASE'
Spring Configuration
okta.oauth2.issuer=https://dev-886281.oktapreview.com/oauth2/default
okta.oauth2.clientId={ clientId }
okta.oauth2.audience=api://default
okta.oauth2.scopeClaim=scp
okta.oauth2.rolesClaim=groups
security.oauth2.resource.user-info-uri=https://dev-886281.oktapreview.com/oauth2/default/v1/userinfo
Okta Service Configuration
Application type: Single Page App (SPA)
Allowed grant types: Implicit
Allow Access Token with implicit grant type: true
Controller
#RestController
#RequestMapping("/products")
public class ProductController {
...
#GetMapping
public ResponseEntity<List<ProductEntity>> getAllProducts(Principal principal) {
SpringBoot
#EnableResourceServer
#SpringBootApplication
public class CartyApplication {
public static void main(String[] args) {
SpringApplication.run(CartyApplication.class, args);
}
#EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
#Bean
protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() {
return new ResourceServerConfigurerAdapter() {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.anyRequest().authenticated();
}
};
}
Once again the overall integration is working fine, users can only access protected resources once they've signed in via okta, I'm just wondering how to get the users details from okta on the controller.
Thanks in advance.
P.S soz for the code dump
EDIT: Removed snippets and added full CartyApplication class
EDIT2: Added repo - https://github.com/Verric/carty-temp
I have a feeling you might be missing this:
#EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
I'm guessing should remove the .antMatchers("/**").permitAll() line.
See: https://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#CO3-2
I'm guessing you want to protect all/most of your endpoints? I'd recommend only allowing specific routes, and protecting everything else.

spring boot ldap additional details

I have an application configured with LDAP and it works perfectly fine. But now I would need to pull details of users manager. I am not able to get the details please find the details
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws
Exception {
auth
.ldapAuthentication()
.userSearchBase("dc=ab,dc=test,dc=com")
.userSearchFilter("TestName={0}")
.groupSearchBase("ou=Groups,dc=ab,dc=test,dc=com")
.groupSearchFilter("member={1}")
.groupRoleAttribute("cn")
.userDetailsContextMapper(userContextMapper())
.contextSource(contextSource());
}
#Bean
public LdapContextSource contextSource() {
LdapContextSource contextSource= new LdapContextSource();
contextSource.setUrl("ldap:test");
contextSource.setUserDn("CN=,OU=Accounts,DC=ab,DC=test,DC=com");
contextSource.setPassword();
return contextSource;
}
#Bean
public InetOrgPersonContextMapper userContextMapper() {
return new InetOrgPersonContextMapper();
}
I am able to fetch user but I need users manager details.
Try this, you can get all authenticated user details from the class InetOrgPerson ! due to the implementation of your bean userContextMapper.
public void printDetails() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
InetOrgPerson person = (InetOrgPerson)(authentication.getPrincipal());
System.out.println(person.getUsername());
System.out.println(person.getGivenName());
System.out.println(person.getHomePhone());
}
// various methods are implemented in the inetOrgPerson class to getting more ldap informations

Understanding Spring Boot's Oauth2 starter

I started off looking at the Oauth2 starter project and minimal configuration.
https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/jdbc/src/main/java/demo/Application.java
All the examples either use in memory configuration or jdbc configuration for storing client roles (e.g ClientDetailsServiceConfigurer) . In my case the details should come in LDAP. So I have two questions.
How do override the default to go to ldap instead of memory or jdbc.
In general , where how do I unravel the Spring Boot thread and read the starter source code and how to change default config ? All I see is a high level annotation.
org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer
This indirection in Spring Boot makes it extremely difficult to follow and scant documentation doesn't help. Or maybe I am missing something?
thanks !!! this has been bugging me for a while.
To implement Oauth2 with LDAP, you may follow this tutorial : https://raymondhlee.wordpress.com/2015/12/05/oauth2-authorization-server-with-spring-security.
You may also take a look a this other question: spring-security-oauth2 2.0.7 refresh token UserDetailsService Configuration - UserDetailsService is required
As for your other question "I want to follow the request and see what components get invoked and when": I suggest you add logging.
(1) Add logging in every method
(2) Set log level for security package in application.properties:
logging.level.org.springframework.security=DEBUG
(3) Add CommonsRequestLoggingFilter:
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
LOGGER.info("Creating CommonsRequestLoggingFilter");
CommonsRequestLoggingFilter crlf = new CommonsRequestLoggingFilter();
crlf.setIncludeClientInfo(true);
crlf.setIncludeQueryString(true);
crlf.setIncludePayload(true);
return crlf;
}
(4) Add log level for CommonsRequestLoggingFilter (in application.properties):
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
For the OAuth/LDAP tutorial, here's the notable parts (quoted from https://raymondhlee.wordpress.com/2015/12/05/oauth2-authorization-server-with-spring-security):
Authorization Server Configuration Below is my implementation of the
AuthorizationServerConfigurerAdapter. The database schema for JDBC
client details and token services can be found in here.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new JdbcTokenStore(dataSource)).authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
Login Security Configuration Below is the security configuration
handling user authorization.
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE) // note 1
public class LoginConfig extends WebSecurityConfigurerAdapter {
#Value("${ldap.domain}")
private String DOMAIN;
#Value("${ldap.url}")
private String URL;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel().anyRequest().requiresSecure();
// Only requests matching regex are handled by this security configurer
http.requestMatchers().regexMatchers("/login", "/login.+", "/oauth/.+", "/j_spring_security_check", "/logout"); //
AuthenticationEntryPoint entryPoint = entryPoint();
http.exceptionHandling().authenticationEntryPoint(entryPoint);
http.formLogin(); // note 3i
http.addFilter(usernamePasswordAuthenticationFilter());
http.authorizeRequests().antMatchers("/login").permitAll();
http.authorizeRequests().antMatchers("/oauth/**").authenticated();
http.authorizeRequests().antMatchers("/j_spring_security_check").anonymous().and().csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception { // note 4
authManagerBuilder.parentAuthenticationManager(authenticationManager());
}
protected AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
private AuthenticationEntryPoint entryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
private UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() {
UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager();
AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?login_error=true");
filter.setAuthenticationFailureHandler(failureHandler);
return filter;
}
}

Spring boot + Spring LDAP + Service Account + Bad Credentials

i am getting bad credentials exception when using active directory service account and the same code is working fine for user account.
And there is nothing wrong with service user credentials.
Please find my code below.
#Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
"abc.def.ghi", "ldap://abc.def.ghi:389");
auth.authenticationProvider(provider);
}
}
The userPrincipleName property is not inline with expected format username#domainname for that specific user account.

Resources