Categories
Java Linux

How to Create JWT Token Using LDAP and Spring Boot Part 1

If you are working in an enterprise infrastructures, chances are that you are using a centralized authentication system, most likely Active Directory or openLDAP. In this blog I’ll explore how to create a REST API using spring boot to authenticate against openLDAP and create a JWT token in return.

Before getting our hand dirty, we need to review the architecture of spring security and the way we want to utilise it, in a REST API endpoint. According to openLDAP, I’ve explained it’s concept briefly before, you can read more about it here. Also I’ll assume that you know how Spring Boot and JWT works.

Spring Security

In this example I will extend the WebSecurityConfigurerAdapter. This class will assist me to intercept security chain of spring security and insert openLDAP authentication adapter in between.

In fact this abstract class provides convenient methods for configuring spring security configuration using HTTPSecurity object.

First of all I injected three different beans as follows :

   private OpenLdapAuthenticationProvider openLdapAuthenticationProvider;
   private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
   private JwtRequestFilter jwtRequestFilter;

Then override the configure(AuthenticationManagerBuilder auth) :

 @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(openLdapAuthenticationProvider);
    }

This will let us to override default behaviour of spring security authentication. In addition we need to override the configure(HttpSecurity httpSecurity):

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // We don't need CSRF for this example
        httpSecurity
                .csrf().disable()
                .headers()
                .frameOptions()
                .deny()
                .and()
                // dont authenticate this particular request
                .authorizeRequests().antMatchers("/api/login").permitAll().
                // all other requests need to be authenticated
                antMatchers("/api/**").authenticated().and().
                // make sure we use stateless session; session won't be used to
                // store user's state.
                exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                // Add a filter to validate the tokens with every request
                httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

Also For the sake of manually authenticating a user in /api/login we will expose the authenticationManagerBean() :

  @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

After configuring WebSecurityConfig, I’ll provide my customer authentication adapter. This adapter will utilise spring’s LdapTemplate and let us to establish a connection to a LDAP server.

@Component
public class OpenLdapAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private LdapContextSource contextSource;

    private LdapTemplate ldapTemplate;

    @PostConstruct
    private void initContext() {
        contextSource.setUrl("ldap://1.1.1.1:389/ou=users,dc=www,dc=devcrutch,dc=com");
// I use anonymous binding so, no need to provide bind user/pass
        contextSource.setAnonymousReadOnly(true);
        contextSource.setUserDn("ou=users,");
        contextSource.afterPropertiesSet();
        ldapTemplate = new LdapTemplate(contextSource);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        Filter filter = new EqualsFilter("uid", authentication.getName());
        Boolean authenticate = ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.encode(),
                authentication.getCredentials().toString());
        if (authenticate) {
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            UserDetails userDetails = new User(authentication.getName() ,authentication.getCredentials().toString()
                    ,grantedAuthorities);
            Authentication auth = new UsernamePasswordAuthenticationToken(userDetails,
                    authentication.getCredentials().toString() , grantedAuthorities);
            return auth;

        } else {
            return null;
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

Another part that we have to take into consideration is, implementing user login controller. Since we haven’t provided any filter for controlling username and password we ought to implementing it manually as follows :

@RestController
@RequestMapping("/api/login")
public class LoginController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserService userService;



    @PostMapping
    public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception
    {
        authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
        final User userDetails = userService.loadUserByUsername(authenticationRequest.getUsername());
        final String token = jwtTokenUtil.generateToken(userDetails);
        return ResponseEntity.ok(new JwtResponse(token));
    }

    private void authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            throw new Exception("USER_DISABLED", e);
        } catch (BadCredentialsException e) {
            throw new Exception("INVALID_CREDENTIALS", e);
        }
    }
}

These are the pillars of having a REST API + JWT + LDAP back-end using spring boot.

Now we can test the API using a REST client.

After getting the JWT token we can call authorized endpoints

You can find a working source code on my github.

For the next part I’ll make this code more concise.

Categories
Java Linux

How to Authenticate Against openLDAP Without Knowing DN Using java

In fact you can’t do it without knowing DN! There is an anonymous access in openLDAP which is enabled by default. The anonymous access let one to query(search filter) openLDAP without knowing bind username/password.

Run following command on your openLDAP server :

ldapwhoami -H ldap:// -x

If you get “anonymous” as result you are all set and your openLDAP is supporting anonymous query, otherwise this blog is not the one you are looking for!

So What’s the Deal?

Assume you know UID of the user in ldap directory but not his DN and assume the root directory hierarchy is : dc=devcrutch,dc=com . How do you want to get CN and then DN of such user in LDAP just by using UID? you might think of such query to get data out of openLDAP

uid=USERNAME,dc=devcrutch,dc=com

If you run this query, it’ll get you to nowhere.

Here Comes the Anonymous Query

First of all you have to query the whole directory for finding such user. You have to create a query similar to following:

(&(objectClass=*)(uid=USERNAME))

This query will search the entire Directory Information Tree (DIT) for such user name.

ldapsearch -x -h 127.0.0.1 -b dc=devcrutch,dc=com "(&(objectClass=*)(uid=USERNAME))"

With such query you can get the DN. Using DN and password you can authenticate against LDAP.

Well this was the idea, applying a search filter using USERNAME via anonymous identity then find the DN and finally login using the retrieved DN.

Time for Some java

Now we have the rough idea it’s time to implement it in java. For finding DN you need to query the entire LDAP directory (note: in real world searching the entire directory is not a good idea, you have to narrow your query, otherwise your query might consume all the server’s resources)

private String findDN(String user) throws Exception {
    DirContext ctx = getContext();
    String dn = null;
    String filter = "(&(objectClass=*)(uid=" + user + "))";
    NamingEnumeration answer = ctx.search("", filter, getSimpleSearchControls());
    if (answer.hasMore()) {
        SearchResult result = (SearchResult) answer.next();
        dn = result.getNameInNamespace();
    }
    answer.close();
    return dn;
}

Then after finding the correct DN you can bind your username and password

public DirContext bind(String username, String password) throws Exception {
        DirContext dirContext;
        Hashtable<String, String> env = new Hashtable<String, String>();
        String dn = findDN(username);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, dn);
        env.put(Context.SECURITY_CREDENTIALS, password);
        try {
            dirContext = getContext(env);
        } catch (javax.naming.AuthenticationException e) {
            throw new Exception(e);
        }
        return dirContext;
}

That’s it.

You can find a working source code on my github