{"id":63,"date":"2017-05-13T23:50:16","date_gmt":"2017-05-13T21:50:16","guid":{"rendered":"http:\/\/xoupix.fr\/wordpress\/?p=63"},"modified":"2017-05-14T22:02:17","modified_gmt":"2017-05-14T20:02:17","slug":"spring-ldap-how-to-love-querying-active-directory","status":"publish","type":"post","link":"https:\/\/blog.xoupix.fr\/index.php\/2017\/05\/13\/spring-ldap-how-to-love-querying-active-directory\/","title":{"rendered":"Spring-ldap, how to love querying active directory"},"content":{"rendered":"<p>Because managing Active Directory entries is not so easy using java API, the\u00a0<a href=\"https:\/\/spring.io\/\">Spring team<\/a>\u00a0provides the\u00a0<a href=\"http:\/\/projects.spring.io\/spring-ldap\/\">spring-ldap<\/a>\u00a0API, which is an amazing library !<\/p>\n<p><!--more--><\/p>\n<h1>What is this API about ?<\/h1>\n<blockquote><p>Spring LDAP is a Java library for simplifying LDAP operations, based on the pattern of Spring&#8217;s JdbcTemplate. The framework relieves the user of common chores, such as looking up and closing contexts, looping through results, encoding\/decoding values and filters, and more.<\/p><\/blockquote>\n<h1>The connection<\/h1>\n<p>This API provides all you will need to query Active Directory simply and easily. Just because I wanted to use my AD object in a multi-threading environment, I tuned\u00a0it using a pooled context source :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">private static PoolingContextSource getContextSource(String url,\r\n  String base, \r\n  String user, \r\n  String password) {\r\n    LdapContextSource contextSource = new LdapContextSource();\r\n    contextSource.setUrl(url);\r\n    contextSource.setBase(base);\r\n    contextSource.setUserDn(user);\r\n    contextSource.setPassword(password);\r\n\r\n    PoolingContextSource pooledContextSource = new PoolingContextSource();\r\n    pooledContextSource.setContextSource(contextSource);\r\n \r\n    return pooledContextSource;\r\n}<\/pre>\n<p>By passing the AD url (e.g. <em>ldap:\/\/hostname:389<\/em>), the base realm to use (you can leave it empty), the user (e.g. can be <em>cn=root<\/em>) and the user password. If you set the base realm to a specific value (as, for example, &#8220;o=sample&#8221;), all dn results will miss this part.<\/p>\n<h2>Reading\u00a0and writing context<\/h2>\n<p>When the context source is defined, you can use it and query it to retrieve a\u00a0read only context with the associated method\u00a0getReadOnlyContext(). Similar to the reading context, you can retrieve a write context by\u00a0using the\u00a0getReadWriteContext() method.<\/p>\n<h1>What about retrieving data ?<\/h1>\n<p>This part is pretty cool. Spring-ldap is based on bean usage, so you will be able to query your AD, and retrieving a specific bean using a mapper.<\/p>\n<h2>The LdapTemplate<\/h2>\n<p>The library allows you to use the LdapTemplate object. Just declare it using the following :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">LdapTemplate ldapTemplate = new LdapTemplate(getPoolingContextSourceReplica());<\/pre>\n<h2>\u00a0You said&#8230; A mapper ?<\/h2>\n<p>Yes, when querying the AD, you can provide a mapper, which will handle the query result, and building a business-related object (you can define a mapper for a user, for a group, for everything else&#8230;) :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">public class UserMapper implements ContextMapper&lt;LDAPUser&gt; {\r\n    @Override\r\n    public User mapFromContext(Object ctx) throws NamingException {\r\n        if (ctx == null) {\r\n            return null;\r\n        }\r\n \r\n        DirContextAdapter context = (DirContextAdapter) ctx;\r\n \r\n        User user = new User();\r\n        user.setCn(context.getAttributes(\"cn\"));\r\n        user.setDn(context.getDn()); \r\n\r\n        return user;\r\n    }\r\n}<\/pre>\n<h2>\u00a0Then the search<\/h2>\n<p>When it&#8217;s done, you can query it using a simple syntax. In this example, I want to get every AD user (objectclass is person) where the cn is &#8220;Doe&#8221; or &#8220;Doo&#8221; :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">ldapTemplate.search(\r\n  query().where(\"objectclass\").is(\"person\")\r\n  .and(query().where(\"cn\").is(\"Doe\").or(\"cn\").is(\"Doo\")),\r\n  new UserMapper());<\/pre>\n<p>By analyzing the search return, I have a User object, with a valorised dn and cn value !<\/p>\n<h1>Full code example<\/h1>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-group=\"LdapSearchExample.java\" data-enlighter-title=\"LdapSearchExample.java\">package fr.xoupix.ldap;\r\n\r\nimport static org.springframework.ldap.query.LdapQueryBuilder.query;\r\n\r\nimport java.util.List;\r\n\r\nimport org.springframework.ldap.core.LdapTemplate;\r\nimport org.springframework.ldap.core.support.LdapContextSource;\r\nimport org.springframework.ldap.pool.factory.PoolingContextSource;\r\n\r\n\/**\r\n * Ldap search example\r\n *\/\r\npublic class LdapSearchExample {\r\n\r\n    \/**\r\n     * Declaring a new connection\r\n     * \r\n     * @param url The LDAP url\r\n     * @param base The LDAP base realm\r\n     * @param user The user login\r\n     * @param password The user password\r\n     * @return A valid {@link PoolingContextSource}\r\n     *\/\r\n    public static PoolingContextSource getContextSource(String url, String base, String user, String password) {\r\n        LdapContextSource contextSource = new LdapContextSource();\r\n        contextSource.setUrl(url);\r\n        contextSource.setBase(base);\r\n        contextSource.setUserDn(user);\r\n        contextSource.setPassword(password);\r\n        \r\n        \/\/ Warning !\r\n        \/\/ We need this call cause we are not using spring-bean\r\n        contextSource.afterPropertiesSet();\r\n\r\n        PoolingContextSource pooledContextSource = new PoolingContextSource();\r\n        pooledContextSource.setContextSource(contextSource);\r\n        \r\n        return pooledContextSource;\r\n    }\r\n\r\n    \/**\r\n     * @param args Unused in the example\r\n     * @throws Exception If there is an error during the search\r\n     *\/\r\n    public static void main(String[] args) throws Exception {\r\n        PoolingContextSource poolingContextSource = LdapSearchExample.getContextSource(\r\n                \"ldap:\/\/192.168.56.101:389\", \"\", \"cn=root\", \"password\");\r\n\r\n        LdapTemplate ldapTemplate = new LdapTemplate(poolingContextSource);\r\n        List&lt;User&gt; userList = ldapTemplate.search(\r\n        \t      query().where(\"objectclass\").is(\"person\")\r\n        \t             .and(query().where(\"cn\").is(\"P8Admin\")),\r\n        \t      new UserMapper());\r\n        for (User user : userList) {\r\n            System.out.println(user);\r\n        }\r\n    }\r\n\r\n}<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"mootools\" data-enlighter-title=\"User.java\" data-enlighter-group=\"User.java\">package fr.xoupix.ldap;\r\n\r\n\/**\r\n * The {@link User} bean\r\n *\/\r\npublic class User {\r\n\r\n    \/\/ The user cn\r\n    private String cn;\r\n    \r\n    \/\/ The user dn\r\n    private String dn;\r\n\r\n    \/**\r\n     * @return the cn\r\n     *\/\r\n    public String getCn() {\r\n        return cn;\r\n    }\r\n\r\n    \/**\r\n     * @param cn the cn to set\r\n     *\/\r\n    public void setCn(String cn) {\r\n        this.cn = cn;\r\n    }\r\n\r\n    \/**\r\n     * @return the dn\r\n     *\/\r\n    public String getDn() {\r\n        return dn;\r\n    }\r\n\r\n    \/**\r\n     * @param dn the dn to set\r\n     *\/\r\n    public void setDn(String dn) {\r\n        this.dn = dn;\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return \"User [cn=\" + this.cn + \", dn=\" + this.dn + \"]\";\r\n    }\r\n\r\n}<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-group=\"UserMapper.java\" data-enlighter-title=\"UserMapper.java\">package fr.xoupix.ldap;\r\n\r\nimport javax.naming.NamingException;\r\n\r\nimport org.springframework.ldap.core.ContextMapper;\r\nimport org.springframework.ldap.core.DirContextAdapter;\r\n\r\n\/**\r\n * The {@link User} mapper\r\n *\/\r\npublic class UserMapper implements ContextMapper&lt;User&gt; {\r\n\r\n    \/**\r\n     * Triggered after a search\r\n     * \r\n     * @param ctx A search result object\r\n     * @return A {@link User} object\r\n     *\/\r\n    public User mapFromContext(Object ctx) throws NamingException {\r\n        \r\n        if (ctx == null) {\r\n            return null;\r\n        }\r\n        \r\n        DirContextAdapter context = (DirContextAdapter) ctx;\r\n        \r\n        User user = new User();\r\n        user.setCn(context.getStringAttribute(\"cn\"));\r\n        user.setDn(context.getDn().toString());\r\n        \r\n        return user;\r\n    }\r\n\r\n}\r\n<\/pre>\n<h2>In console<\/h2>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">User [cn=P8Admin, dn=cn=P8Admin,o=sample]<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Because managing Active Directory entries is not so easy using java API, the\u00a0Spring team\u00a0provides the\u00a0spring-ldap\u00a0API, which is an amazing library !<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-63","post","type-post","status-publish","format-standard","hentry","category-spring"],"_links":{"self":[{"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/posts\/63","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/comments?post=63"}],"version-history":[{"count":13,"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/posts\/63\/revisions"}],"predecessor-version":[{"id":78,"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/posts\/63\/revisions\/78"}],"wp:attachment":[{"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/media?parent=63"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/categories?post=63"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.xoupix.fr\/index.php\/wp-json\/wp\/v2\/tags?post=63"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}