Skip to content

Commit aca3280

Browse files
committed
Add Spring Boot Example
Closes gh-558
1 parent 415cf80 commit aca3280

File tree

8 files changed

+301
-0
lines changed

8 files changed

+301
-0
lines changed

samples/boot/build.gradle

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
plugins {
2+
id 'org.springframework.boot' version '2.6.3'
3+
id 'io.spring.dependency-management'
4+
id 'java'
5+
}
6+
7+
repositories {
8+
mavenCentral()
9+
}
10+
11+
dependencies {
12+
implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
13+
implementation 'org.springframework.boot:spring-boot-starter-security'
14+
implementation 'org.springframework.boot:spring-boot-starter-web'
15+
implementation 'org.springframework.security:spring-security-ldap'
16+
implementation 'com.unboundid:unboundid-ldapsdk'
17+
testImplementation 'org.springframework.boot:spring-boot-starter-test'
18+
testImplementation 'org.springframework.security:spring-security-test'
19+
}
20+
21+
tasks.named('test') {
22+
useJUnitPlatform()
23+
}

samples/boot/readme.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
A Hello World Spring LDAP application using Spring Boot
2+
3+
The application is protected by Spring Security and uses an embedded UnboundID container for its LDAP server.
4+
5+
You can authenticate with HTTP basic using `bob`/`bobspassword`:
6+
7+
```bash
8+
curl --user bob:bobspassword localhost:8080
9+
```
10+
11+
And you should see the response:
12+
13+
```bash
14+
Hello, bob
15+
```
16+
17+
Also, you can hit the `cn` endpoint which uses `LdapTemplate` to query the datastore for the user's `cn` attribute value, like so:
18+
19+
```bash
20+
curl --user bob:bobspassword localhost:8080/cn
21+
```
22+
23+
This should result in:
24+
25+
```bash
26+
[
27+
"Bob Hamilton"
28+
]
29+
```
30+
31+
To run the example, do `./gradlew :bootRun`.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package sample;
2+
3+
import java.util.List;
4+
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.ldap.core.AttributesMapper;
7+
import org.springframework.ldap.core.LdapTemplate;
8+
import org.springframework.security.core.Authentication;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
public class HelloController {
14+
@Autowired
15+
LdapTemplate ldap;
16+
17+
@GetMapping
18+
public String hello(Authentication authentication) {
19+
return "Hello, " + authentication.getName();
20+
}
21+
22+
@GetMapping("/cn")
23+
public List<String> cn(Authentication authentication) {
24+
AttributesMapper<String> mapper = (attrs) -> attrs.get("cn").get().toString();
25+
return this.ldap.search("ou=people", "uid=" + authentication.getName(), mapper);
26+
}
27+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package sample;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.ldap.core.ContextSource;
6+
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
7+
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
8+
import org.springframework.security.ldap.authentication.BindAuthenticator;
9+
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
10+
import org.springframework.security.ldap.authentication.LdapAuthenticator;
11+
import org.springframework.security.ldap.server.UnboundIdContainer;
12+
import org.springframework.security.ldap.userdetails.PersonContextMapper;
13+
14+
@Configuration
15+
public class SecurityConfig {
16+
@Bean
17+
UnboundIdContainer ldapContainer() {
18+
UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:users.ldif");
19+
container.setPort(0);
20+
return container;
21+
}
22+
23+
@Bean
24+
ContextSource contextSource(UnboundIdContainer container) {
25+
int port = container.getPort();
26+
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
27+
}
28+
29+
@Bean
30+
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
31+
BindAuthenticator authenticator = new BindAuthenticator(contextSource);
32+
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
33+
return authenticator;
34+
}
35+
36+
@Bean
37+
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
38+
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
39+
provider.setUserDetailsContextMapper(new PersonContextMapper());
40+
return provider;
41+
}
42+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package sample;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class SpringLdapSimpleSampleApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(SpringLdapSimpleSampleApplication.class, args);
11+
}
12+
13+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
dn: ou=groups,dc=springframework,dc=org
2+
objectclass: top
3+
objectclass: organizationalUnit
4+
ou: groups
5+
6+
dn: ou=subgroups,ou=groups,dc=springframework,dc=org
7+
objectclass: top
8+
objectclass: organizationalUnit
9+
ou: subgroups
10+
11+
dn: ou=people,dc=springframework,dc=org
12+
objectclass: top
13+
objectclass: organizationalUnit
14+
ou: people
15+
16+
dn: ou=space cadets,dc=springframework,dc=org
17+
objectclass: top
18+
objectclass: organizationalUnit
19+
ou: space cadets
20+
21+
dn: ou=\"quoted people\",dc=springframework,dc=org
22+
objectclass: top
23+
objectclass: organizationalUnit
24+
ou: "quoted people"
25+
26+
dn: ou=otherpeople,dc=springframework,dc=org
27+
objectclass: top
28+
objectclass: organizationalUnit
29+
ou: otherpeople
30+
31+
dn: uid=ben,ou=people,dc=springframework,dc=org
32+
objectclass: top
33+
objectclass: person
34+
objectclass: organizationalPerson
35+
objectclass: inetOrgPerson
36+
cn: Ben Alex
37+
sn: Alex
38+
uid: ben
39+
userPassword: $2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36
40+
41+
dn: uid=bob,ou=people,dc=springframework,dc=org
42+
objectclass: top
43+
objectclass: person
44+
objectclass: organizationalPerson
45+
objectclass: inetOrgPerson
46+
cn: Bob Hamilton
47+
sn: Hamilton
48+
uid: bob
49+
userPassword: bobspassword
50+
51+
dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
52+
objectclass: top
53+
objectclass: person
54+
objectclass: organizationalPerson
55+
objectclass: inetOrgPerson
56+
cn: Joe Smeth
57+
sn: Smeth
58+
uid: joe
59+
userPassword: joespassword
60+
61+
dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
62+
objectclass: top
63+
objectclass: person
64+
objectclass: organizationalPerson
65+
objectclass: inetOrgPerson
66+
cn: Mouse, Jerry
67+
sn: Mouse
68+
uid: jerry
69+
userPassword: jerryspassword
70+
71+
dn: cn=slash/guy,ou=people,dc=springframework,dc=org
72+
objectclass: top
73+
objectclass: person
74+
objectclass: organizationalPerson
75+
objectclass: inetOrgPerson
76+
cn: slash/guy
77+
sn: Slash
78+
uid: slashguy
79+
userPassword: slashguyspassword
80+
81+
dn: cn=quote\"guy,ou=\"quoted people\",dc=springframework,dc=org
82+
objectclass: top
83+
objectclass: person
84+
objectclass: organizationalPerson
85+
objectclass: inetOrgPerson
86+
cn: quote\"guy
87+
sn: Quote
88+
uid: quoteguy
89+
userPassword: quoteguyspassword
90+
91+
dn: uid=space cadet,ou=space cadets,dc=springframework,dc=org
92+
objectclass: top
93+
objectclass: person
94+
objectclass: organizationalPerson
95+
objectclass: inetOrgPerson
96+
cn: Space Cadet
97+
sn: Cadet
98+
uid: space cadet
99+
userPassword: spacecadetspassword
100+
101+
102+
103+
dn: cn=developers,ou=groups,dc=springframework,dc=org
104+
objectclass: top
105+
objectclass: groupOfUniqueNames
106+
cn: developers
107+
ou: developer
108+
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
109+
uniqueMember: uid=bob,ou=people,dc=springframework,dc=org
110+
111+
dn: cn=managers,ou=groups,dc=springframework,dc=org
112+
objectclass: top
113+
objectclass: groupOfUniqueNames
114+
cn: managers
115+
ou: manager
116+
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
117+
uniqueMember: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
118+
119+
dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
120+
objectclass: top
121+
objectclass: groupOfUniqueNames
122+
cn: submanagers
123+
ou: submanager
124+
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package sample;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
7+
import org.springframework.boot.test.context.SpringBootTest;
8+
import org.springframework.http.HttpHeaders;
9+
import org.springframework.test.web.servlet.MockMvc;
10+
11+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
12+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
13+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
14+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
15+
16+
@SpringBootTest
17+
@AutoConfigureMockMvc
18+
class SpringLdapSimpleSampleApplicationTests {
19+
20+
@Autowired
21+
MockMvc mvc;
22+
23+
@Test
24+
void indexWhenCorrectUsernameAndPasswordThenAuthenticates() throws Exception {
25+
HttpHeaders http = new HttpHeaders();
26+
http.setBasicAuth("bob", "bobspassword");
27+
this.mvc.perform(get("/").headers(http))
28+
.andExpect(status().isOk())
29+
.andExpect(content().string("Hello, bob"));
30+
}
31+
32+
@Test
33+
void cnWhenCorrectUsernameAndPasswordThenShowsCommonName() throws Exception {
34+
HttpHeaders http = new HttpHeaders();
35+
http.setBasicAuth("bob", "bobspassword");
36+
this.mvc.perform(get("/cn").headers(http))
37+
.andExpect(status().isOk())
38+
.andExpect(jsonPath("$.[0]").value("Bob Hamilton"));
39+
}
40+
}

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ include 'test/integration-tests-sunone'
3737
include 'test/integration-tests-ad'
3838
include 'samples/plain'
3939
include 'samples/odm'
40+
include 'samples/boot'
4041

4142

4243
rootProject.children.each { p->

0 commit comments

Comments
 (0)