├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── ai │ └── auth │ └── jwt │ ├── Application.java │ ├── config │ ├── AdditionalWebConfig.java │ ├── AuthorizationServerConfig.java │ ├── ResourceServerConfig.java │ └── SecurityConfig.java │ ├── controller │ └── ResourceController.java │ ├── domain │ ├── RandomCity.java │ ├── Role.java │ └── User.java │ ├── repository │ ├── RandomCityRepository.java │ ├── RoleRepository.java │ └── UserRepository.java │ └── service │ ├── GenericService.java │ └── impl │ ├── AppUserDetailsService.java │ └── GenericServiceImpl.java └── resources ├── application.properties ├── data.sql └── ldap-data.ldif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | ### Maven template 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | .mvn/timing.properties 12 | 13 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 14 | !/.mvn/wrapper/maven-wrapper.jar 15 | 16 | *.iml 17 | .idea/ 18 | *.log 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Oauth2 using JWT 2 | An Example of Spring Boot Application for Securing a REST API with Oauth2 using JSON Web Token (JWT). JdbcTokenStore is used to save the token issued to the users
3 | It supports multiple authentication providers LDAP and DB. First will check the DB and if the user doesn't exists in DB then it will check the LDAP which is configured. 4 | This application can be used as a seed to quick start your spring boot REST API project with a fully functional security module. 5 | --------------------- 6 | Main building blocks 7 | --------------------- 8 | Spring Boot 1.5.9.RELEASE go to [http://docs.spring.io/spring-boot/docs/1.5.3.RELEASE/reference/htmlsingle/](http://docs.spring.io/spring-boot/docs/1.5.3.RELEASE/reference/htmlsingle/) to learn more about spring boot
9 | JSON Web Token go to []() https://jwt.io/ to decode your generated token and learn more.
10 | 11 | 12 | To run the application
13 | Use one of the several ways of running a Spring Boot application. Below are just three options:
14 | 15 | 1. Build using maven goal: mvn clean package and execute the resulting artifact as follows java -jar springboot-jwt-1.0-SNAPSHOT.jar or
16 | 2. On Unix/Linux based systems: run mvn clean package then run the resulting jar as any other executable ./springboot-jwt-1.0-SNAPSHOT.jar
17 | 18 | To test the application 19 | 20 | First you will need the following basic pieces of information:
21 | - client: testjwtclientid
22 | - secret: XY7kmzoNzl100
23 | - Local DB 24 | - Non-admin username and password: john.doe and jwtpass 25 | - Admin user: admin.admin and jwtpass 26 | - LDAP 27 | - username and password: ben and benspassword 28 | - Example of resource accessible to all authenticated users: [http://localhost:8080/springjwt/cities](http://localhost:8080/springjwt/cities) 29 | - Example of resource accessible to only an admin user: [http://localhost:8080/springjwt/users](http://localhost:8080/springjwt/users) 30 | 31 | 1. Generate an access token 32 | 33 | Use the following generic command to generate an access token: 34 | 35 | ``` 36 | $ curl client:secret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=pwd 37 | ``` 38 | 39 | For this specific application, to generate an access token for the non-admin user john.doe, run: 40 | ``` 41 | $ curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=jwtpass 42 | ``` 43 | You'll receive a response similar to below 44 | 45 | ``` 46 | { 47 | "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDM2MDI2NjksInVzZXJfbmFtZSI6ImFkbWluLmFkbWluIiwiYXV0aG9yaXRpZXMiOlsiU1RBTkRBUkRfVVNFUiIsIkFETUlOX1VTRVIiXSwianRpIjoiYzU0OTUwOWMtOGE2Ni00MmM4LTk4ZDQtZTIxOGMwMmQxYmFiIiwiY2xpZW50X2lkIjoidGVzdGp3dGNsaWVudGlkIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl19.hltbUwoJN9IQRLEHs_RnJS_MBaVMMzp0CjRB6paVGpY", 48 | "token_type": "bearer", 49 | "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbi5hZG1pbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiJjNTQ5NTA5Yy04YTY2LTQyYzgtOThkNC1lMjE4YzAyZDFiYWIiLCJleHAiOjE1NDYxOTM3NjksImF1dGhvcml0aWVzIjpbIlNUQU5EQVJEX1VTRVIiLCJBRE1JTl9VU0VSIl0sImp0aSI6IjU4MTkzMTNmLTg0YTAtNGM1Mi05ZGNiLThiZWM1ZTcwNWI1NiIsImNsaWVudF9pZCI6InRlc3Rqd3RjbGllbnRpZCJ9.yvZmi5SyVmXpVEFwyE2H2lZ6VuP4ZMYQ8udnPtiWIIs", 50 | "expires_in": 899, 51 | "scope": "read write", 52 | "jti": "c549509c-8a66-42c8-98d4-e218c02d1bab" 53 | } 54 | 55 | ``` 56 | 57 | Use the following command to validate an access token: 58 | 59 | ``` 60 | $ curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/check_token -d 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDM2MDI2NjksInVzZXJfbmFtZSI6ImFkbWluLmFkbWluIiwiYXV0aG9yaXRpZXMiOlsiU1RBTkRBUkRfVVNFUiIsIkFETUlOX1VTRVIiXSwianRpIjoiYzU0OTUwOWMtOGE2Ni00MmM4LTk4ZDQtZTIxOGMwMmQxYmFiIiwiY2xpZW50X2lkIjoidGVzdGp3dGNsaWVudGlkIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl19.hltbUwoJN9IQRLEHs_RnJS_MBaVMMzp0CjRB6paVGpY&undefined=' 61 | ``` 62 | Use the following command to refresh token: 63 | 64 | ``` 65 | $ curl -X POST testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/check_token -d 'grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb2huLmRvZSIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiJkYmZiYjI5Mi00NjMyLTQ5ODEtYThjMi0xZjYxNGQ5MjgyY2QiLCJleHAiOjE1NDYxODk3NjcsImF1dGhvcml0aWVzIjpbIlNUQU5EQVJEX1VTRVIiXSwianRpIjoiZjdhZDNiYWMtOTViYy00ZTZhLTkzYTQtODg0NzQ0Y2M5ODc1IiwiY2xpZW50X2lkIjoidGVzdGp3dGNsaWVudGlkIn0.VD9AJeK6555CYk5DaKj7ik81c81C6gPypNhTlEs6bsY&undefined=' 66 | ``` 67 | 2. Use the token to access resources through your RESTful API 68 | 69 | Access content available to all authenticated users 70 | Use the generated token as the value of the Bearer in the Authorization header as follows: 71 | ``` 72 | http://localhost:8080/springjwt/cities -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiYWRtaW4uYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk0NDU0MjgyLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF9VU0VSIiwiQURNSU5fVVNFUiJdLCJqdGkiOiIwYmQ4ZTQ1MC03ZjVjLTQ5ZjMtOTFmMC01Nzc1YjdiY2MwMGYiLCJjbGllbnRfaWQiOiJ0ZXN0and0Y2xpZW50aWQifQ.rvEAa4dIz8hT8uxzfjkEJKG982Ree5PdUW17KtFyeec" 73 | ``` 74 | 75 | Access content available only to an admin user 76 | As with the previous example first generate an access token for the admin user with the credentials provided above then run: 77 | ``` 78 | curl http://localhost:8080/springjwt/users -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiYWRtaW4uYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk0NDU0OTIzLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF9VU0VSIiwiQURNSU5fVVNFUiJdLCJqdGkiOiIyMTAzMjRmMS05MTE0LTQ1NGEtODRmMy1hZjUzZmUxNzdjNzIiLCJjbGllbnRfaWQiOiJ0ZXN0and0Y2xpZW50aWQifQ.OuprVlyNnKuLkoQmP8shP38G3Hje91GBhu4E0HD2Fes" 79 | ``` 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ai.auth.spring.jwt 8 | springboot-jwt 9 | 1.0-SNAPSHOT 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.5.9.RELEASE 14 | 15 | 16 | 17 | UTF-8 18 | UTF-8 19 | 1.8 20 | 1.6.0.RELEASE 21 | Dalston.SR5 22 | 2.7.0 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-jdbc 37 | 38 | 39 | 40 | 41 | org.postgresql 42 | postgresql 43 | 42.2.1.jre7 44 | 45 | 46 | 47 | 48 | io.pivotal.spring.cloud 49 | spring-cloud-services-starter-config-client 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-actuator 55 | 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-aop 61 | 62 | 63 | org.springframework.cloud 64 | spring-cloud-starter-sleuth 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-web 70 | 71 | 72 | 73 | 74 | io.springfox 75 | springfox-swagger2 76 | ${swagger.version} 77 | 78 | 79 | io.springfox 80 | springfox-swagger-ui 81 | ${swagger.version} 82 | compile 83 | 84 | 85 | 86 | 87 | de.codecentric 88 | spring-boot-admin-starter-client 89 | 1.3.0 90 | 91 | 92 | org.springframework.boot 93 | spring-boot-starter-test 94 | test 95 | 96 | 97 | 98 | 99 | org.apache.commons 100 | commons-lang3 101 | 3.8 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-starter-security 107 | 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-devtools 112 | runtime 113 | 114 | 115 | org.springframework.security 116 | spring-security-jwt 117 | 118 | 119 | org.springframework.security.oauth 120 | spring-security-oauth2 121 | 122 | 123 | com.unboundid 124 | unboundid-ldapsdk 125 | 126 | 127 | org.springframework.security 128 | spring-security-ldap 129 | 130 | 131 | org.springframework.boot 132 | spring-boot-starter-data-ldap 133 | 134 | 135 | 136 | 137 | 138 | 139 | org.springframework.cloud 140 | spring-cloud-dependencies 141 | ${spring-cloud.version} 142 | pom 143 | import 144 | 145 | 146 | io.pivotal.spring.cloud 147 | spring-cloud-services-dependencies 148 | ${spring-cloud-services.version} 149 | pom 150 | import 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | org.springframework.boot 159 | spring-boot-maven-plugin 160 | 161 | 162 | 163 | build-info 164 | repackage 165 | 166 | 167 | 168 | ${maven.compiler.target} 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | pl.project13.maven 177 | git-commit-id-plugin 178 | 179 | false 180 | 181 | 182 | 183 | maven-release-plugin 184 | 2.4.2 185 | 186 | 187 | org.apache.maven.scm 188 | maven-scm-provider-gitexe 189 | 1.8.1 190 | 191 | 192 | 193 | false 194 | true 195 | 196 | 197 | 198 | 199 | 200 | org.apache.maven.wagon 201 | wagon-webdav-jackrabbit 202 | 2.10 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | org.apache.maven.plugins 211 | maven-project-info-reports-plugin 212 | 2.9 213 | 214 | 215 | 216 | 217 | dependencies 218 | dependency-convergence 219 | dependency-info 220 | dependency-management 221 | distribution-management 222 | index 223 | license 224 | project-team 225 | scm 226 | summary 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 2018 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/Application.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | /** 8 | * Created by suman.das on 11/28/18. 9 | */ 10 | @SpringBootApplication 11 | @ComponentScan("ai.auth.jwt.*") 12 | public class Application { 13 | public static void main(String[] args) { 14 | SpringApplication.run(Application.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/config/AdditionalWebConfig.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.cors.CorsConfiguration; 6 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 7 | import org.springframework.web.filter.CorsFilter; 8 | 9 | 10 | /** 11 | * Created by suman.das on 11/28/18. 12 | */ 13 | @Configuration 14 | public class AdditionalWebConfig { 15 | /** 16 | * Allowing all origins, headers and methods here is only intended to keep this example simple. 17 | * This is not a default recommended configuration. Make adjustments as 18 | * necessary to your use case. 19 | * 20 | */ 21 | @Bean 22 | public CorsFilter corsFilter() { 23 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 24 | CorsConfiguration config = new CorsConfiguration(); 25 | config.setAllowCredentials(true); 26 | config.addAllowedOrigin("*"); 27 | config.addAllowedHeader("*"); 28 | config.addAllowedMethod("*"); 29 | source.registerCorsConfiguration("/**", config); 30 | return new CorsFilter(source); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/config/AuthorizationServerConfig.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.jdbc.core.JdbcTemplate; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 8 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 9 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 10 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 11 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 12 | import org.springframework.security.oauth2.provider.token.TokenStore; 13 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 14 | 15 | /** 16 | * Created by suman.das on 11/28/18. 17 | */ 18 | @Configuration 19 | @EnableAuthorizationServer 20 | public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 21 | 22 | @Autowired 23 | private TokenStore tokenStore; 24 | 25 | @Autowired 26 | private JwtAccessTokenConverter accessTokenConverter; 27 | 28 | @Autowired 29 | private AuthenticationManager authenticationManager; 30 | 31 | //@Autowired 32 | //private UserDetailsService userDetailsService; 33 | 34 | @Autowired 35 | private JdbcTemplate jdbcTemplate; 36 | 37 | @Override 38 | public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { 39 | configurer 40 | .jdbc(jdbcTemplate.getDataSource()); 41 | } 42 | 43 | 44 | @Override 45 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 46 | endpoints.tokenStore(tokenStore) 47 | .reuseRefreshTokens(false) 48 | .accessTokenConverter(accessTokenConverter) 49 | .authenticationManager(authenticationManager) 50 | ; 51 | 52 | 53 | } 54 | 55 | 56 | @Override 57 | public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { 58 | oauthServer.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); 59 | } 60 | 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 8 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 9 | import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; 10 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 11 | 12 | /** 13 | * Created by suman.das on 11/28/18. 14 | */ 15 | @Configuration 16 | @EnableResourceServer 17 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 18 | 19 | @Autowired 20 | private ResourceServerTokenServices tokenServices; 21 | 22 | @Autowired 23 | private JwtAccessTokenConverter accessTokenConverter; 24 | 25 | @Override 26 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 27 | resources.tokenServices(tokenServices); 28 | } 29 | 30 | @Override 31 | public void configure(HttpSecurity http) throws Exception { 32 | http 33 | .requestMatchers() 34 | .and() 35 | .authorizeRequests() 36 | .antMatchers("/actuator/**", "/api-docs/**","/oauth/*").permitAll() 37 | .antMatchers("/jwttest/**" ).authenticated(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Primary; 8 | import org.springframework.jdbc.core.JdbcTemplate; 9 | import org.springframework.security.authentication.AuthenticationManager; 10 | import org.springframework.security.authentication.encoding.LdapShaPasswordEncoder; 11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 12 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 16 | import org.springframework.security.config.http.SessionCreationPolicy; 17 | import org.springframework.security.core.userdetails.UserDetailsService; 18 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 19 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 20 | import org.springframework.security.oauth2.provider.token.TokenStore; 21 | import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; 22 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 23 | import org.springframework.web.cors.CorsConfiguration; 24 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 25 | import org.springframework.web.filter.CorsFilter; 26 | 27 | /** 28 | * Created by suman.das on 11/28/18. 29 | */ 30 | @Configuration 31 | @EnableWebSecurity 32 | @EnableGlobalMethodSecurity(prePostEnabled = true) 33 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 34 | 35 | @Value("${security.signing-key}") 36 | private String signingKey; 37 | 38 | @Value("${security.encoding-strength}") 39 | private Integer encodingStrength; 40 | 41 | @Value("${security.security-realm}") 42 | private String securityRealm; 43 | 44 | @Autowired 45 | private JdbcTemplate jdbcTemplate; 46 | 47 | @Autowired 48 | private UserDetailsService userDetailsService; 49 | 50 | @Bean 51 | @Override 52 | protected AuthenticationManager authenticationManager() throws Exception { 53 | return super.authenticationManager(); 54 | } 55 | 56 | @Bean 57 | public BCryptPasswordEncoder passwordEncoder() { 58 | return new BCryptPasswordEncoder(); 59 | } 60 | 61 | @Override 62 | protected void configure(HttpSecurity http) throws Exception { 63 | http 64 | .sessionManagement() 65 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 66 | .and() 67 | .httpBasic() 68 | .realmName(securityRealm) 69 | .and() 70 | .csrf() 71 | .disable(); 72 | 73 | } 74 | 75 | @Bean 76 | public JwtAccessTokenConverter accessTokenConverter() { 77 | JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); 78 | converter.setSigningKey(signingKey); 79 | return converter; 80 | } 81 | 82 | // Spring has UserDetailsService interface, which can be overriden to provide our implementation for fetching user from database (or any other source). 83 | // The UserDetailsService object is used by the auth manager to load the user from database. 84 | // In addition, we need to define the password encoder also. So, auth manager can compare and verify passwords. 85 | @Override 86 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 87 | auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 88 | 89 | auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups") 90 | .contextSource().url("ldap://localhost:8389/dc=springframework,dc=org") 91 | .and() 92 | .passwordCompare() 93 | .passwordEncoder(new LdapShaPasswordEncoder()) 94 | .passwordAttribute("userPassword"); 95 | 96 | } 97 | 98 | 99 | @Bean 100 | public TokenStore tokenStore() { 101 | return new JdbcTokenStore(jdbcTemplate.getDataSource()); 102 | } 103 | 104 | @Bean 105 | @Primary 106 | //Making this primary to avoid any accidental duplication with another token service instance of the same name 107 | public DefaultTokenServices tokenServices() { 108 | DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); 109 | defaultTokenServices.setTokenStore(tokenStore()); 110 | defaultTokenServices.setSupportRefreshToken(true); 111 | return defaultTokenServices; 112 | } 113 | 114 | @Bean 115 | public CorsFilter corsFilter() { 116 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 117 | CorsConfiguration config = new CorsConfiguration(); 118 | config.setAllowCredentials(true); 119 | config.addAllowedOrigin("*"); 120 | config.addAllowedHeader("*"); 121 | config.addAllowedMethod("*"); 122 | source.registerCorsConfiguration("/**", config); 123 | return new CorsFilter(source); 124 | } 125 | 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/controller/ResourceController.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.controller; 2 | 3 | import ai.auth.jwt.domain.RandomCity; 4 | import ai.auth.jwt.domain.User; 5 | import ai.auth.jwt.service.GenericService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by suman.das on 11/28/18. 15 | */ 16 | @RestController 17 | @RequestMapping("/jwttest") 18 | public class ResourceController { 19 | @Autowired 20 | private GenericService userService; 21 | 22 | @RequestMapping(value ="/cities") 23 | //@PreAuthorize("hasAuthority('ADMIN_USER') or hasAuthority('STANDARD_USER')") 24 | public List getUser(){ 25 | return userService.findAllRandomCities(); 26 | } 27 | 28 | @RequestMapping(value ="/users", method = RequestMethod.GET) 29 | //@PreAuthorize("hasAuthority('ADMIN_USER')") 30 | public List getUsers(){ 31 | return userService.findAllUsers(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/domain/RandomCity.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.domain; 2 | 3 | import javax.persistence.*; 4 | 5 | /** 6 | * Created by suman.das on 11/28/18. 7 | */ 8 | @Entity 9 | @Table(name = "random_city") 10 | public class RandomCity { 11 | @Id 12 | @GeneratedValue(strategy = GenerationType.IDENTITY) 13 | @Column(name = "id") 14 | private Long id; 15 | 16 | @Column(name = "name") 17 | private String name; 18 | 19 | public Long getId() { 20 | return id; 21 | } 22 | 23 | public void setId(Long id) { 24 | this.id = id; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/domain/Role.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.domain; 2 | 3 | import javax.persistence.*; 4 | 5 | /** 6 | * Created by suman.das on 11/28/18. 7 | */ 8 | @Entity 9 | @Table(name="app_role") 10 | public class Role { 11 | private static final long serialVersionUID = 1L; 12 | @Id 13 | @GeneratedValue(strategy = GenerationType.IDENTITY) 14 | private Long id; 15 | 16 | @Column(name="role_name") 17 | private String roleName; 18 | 19 | @Column(name="description") 20 | private String description; 21 | 22 | 23 | public Long getId() { 24 | return id; 25 | } 26 | 27 | public void setId(Long id) { 28 | this.id = id; 29 | } 30 | 31 | public String getRoleName() { 32 | return roleName; 33 | } 34 | 35 | public void setRoleName(String roleName) { 36 | this.roleName = roleName; 37 | } 38 | 39 | public String getDescription() { 40 | return description; 41 | } 42 | 43 | public void setDescription(String description) { 44 | this.description = description; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/domain/User.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import javax.persistence.*; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by suman.das on 11/28/18. 10 | */ 11 | @Entity 12 | @Table(name = "app_user") 13 | public class User { 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | @Column(name = "id") 17 | private Long id; 18 | 19 | @Column(name = "username") 20 | private String username; 21 | 22 | @Column(name = "password") 23 | @JsonIgnore 24 | private String password; 25 | 26 | @Column(name = "first_name") 27 | private String firstName; 28 | 29 | @Column(name = "last_name") 30 | private String lastName; 31 | 32 | /** 33 | * Roles are being eagerly loaded here because 34 | * they are a fairly small collection of items for this example. 35 | */ 36 | @ManyToMany(fetch = FetchType.EAGER) 37 | @JoinTable(name = "user_role", joinColumns 38 | = @JoinColumn(name = "user_id", 39 | referencedColumnName = "id"), 40 | inverseJoinColumns = @JoinColumn(name = "role_id", 41 | referencedColumnName = "id")) 42 | private List roles; 43 | 44 | public Long getId() { 45 | return id; 46 | } 47 | 48 | public void setId(Long id) { 49 | this.id = id; 50 | } 51 | 52 | public String getUsername() { 53 | return username; 54 | } 55 | 56 | public void setUsername(String username) { 57 | this.username = username; 58 | } 59 | 60 | public String getPassword() { 61 | return password; 62 | } 63 | 64 | public void setPassword(String password) { 65 | this.password = password; 66 | } 67 | 68 | public String getFirstName() { 69 | return firstName; 70 | } 71 | 72 | public void setFirstName(String firstName) { 73 | this.firstName = firstName; 74 | } 75 | 76 | public String getLastName() { 77 | return lastName; 78 | } 79 | 80 | public void setLastName(String lastName) { 81 | this.lastName = lastName; 82 | } 83 | 84 | public List getRoles() { 85 | return roles; 86 | } 87 | 88 | public void setRoles(List roles) { 89 | this.roles = roles; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/repository/RandomCityRepository.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.repository; 2 | 3 | import ai.auth.jwt.domain.RandomCity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * Created by suman.das on 11/28/18. 8 | */ 9 | public interface RandomCityRepository extends JpaRepository { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.repository; 2 | 3 | import ai.auth.jwt.domain.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * Created by suman.das on 11/28/18. 8 | */ 9 | public interface RoleRepository extends JpaRepository { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.repository; 2 | 3 | import ai.auth.jwt.domain.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * Created by suman.das on 11/28/18. 8 | */ 9 | public interface UserRepository extends JpaRepository { 10 | User findByUsername(String username); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/service/GenericService.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.service; 2 | 3 | import ai.auth.jwt.domain.RandomCity; 4 | import ai.auth.jwt.domain.User; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by suman.das on 11/28/18. 10 | */ 11 | public interface GenericService { 12 | User findByUsername(String username); 13 | 14 | List findAllUsers(); 15 | 16 | List findAllRandomCities(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/service/impl/AppUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.service.impl; 2 | 3 | import ai.auth.jwt.domain.User; 4 | import ai.auth.jwt.repository.UserRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by suman.das on 11/28/18. 18 | */ 19 | @Component 20 | public class AppUserDetailsService implements UserDetailsService{ 21 | @Autowired 22 | private UserRepository userRepository; 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { 26 | User user = userRepository.findByUsername(s); 27 | 28 | if(user == null) { 29 | throw new UsernameNotFoundException(String.format("The username %s doesn't exist", s)); 30 | } 31 | 32 | List authorities = new ArrayList<>(); 33 | user.getRoles().forEach(role -> { 34 | authorities.add(new SimpleGrantedAuthority(role.getRoleName())); 35 | }); 36 | 37 | UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); 38 | 39 | return userDetails; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/ai/auth/jwt/service/impl/GenericServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ai.auth.jwt.service.impl; 2 | 3 | import ai.auth.jwt.domain.RandomCity; 4 | import ai.auth.jwt.domain.User; 5 | import ai.auth.jwt.repository.RandomCityRepository; 6 | import ai.auth.jwt.repository.UserRepository; 7 | import ai.auth.jwt.service.GenericService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by suman.das on 11/28/18. 15 | */ 16 | @Service 17 | public class GenericServiceImpl implements GenericService { 18 | @Autowired 19 | private UserRepository userRepository; 20 | 21 | @Autowired 22 | private RandomCityRepository randomCityRepository; 23 | 24 | @Override 25 | public User findByUsername(String username) { 26 | return userRepository.findByUsername(username); 27 | } 28 | 29 | @Override 30 | public List findAllUsers() { 31 | return userRepository.findAll(); 32 | } 33 | 34 | @Override 35 | public List findAllRandomCities() { 36 | return randomCityRepository.findAll(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #security.oauth2.resource.filter-order=3 2 | 3 | security.signing-key=MaYzkSjmkzPC57L 4 | security.encoding-strength=256 5 | security.security-realm=Spring Boot JWT Example Realm 6 | 7 | #security.jwt.client-id=testjwtclientid 8 | #security.jwt.client-secret=XY7kmzoNzl100 9 | #security.jwt.grant-type=password 10 | #security.jwt.scope-read=read 11 | #security.jwt.scope-write=write 12 | #security.jwt.resource-ids=testjwtresourceid 13 | 14 | spring.application.name=jwt-springBoot-oauth 15 | spring.datasource.url=jdbc:postgresql://localhost:5432/auth?ApplicationName=authentication 16 | spring.datasource.username=${DATABASE_USERNAME:postgres} 17 | spring.datasource.password=${DATABASE_PASSWORD:postgres} 18 | spring.datasource.driver-class-name=org.postgresql.Driver 19 | 20 | spring.ldap.embedded.port=8389 21 | spring.ldap.embedded.ldif=classpath:ldap-data.ldif 22 | spring.ldap.embedded.base-dn=dc=springframework,dc=org -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE random_city ( 2 | id bigint NOT NULL, 3 | name varchar(255) DEFAULT NULL, 4 | PRIMARY KEY (id) 5 | ); 6 | 7 | CREATE TABLE app_role ( 8 | id bigint NOT NULL , 9 | description varchar(255) DEFAULT NULL, 10 | role_name varchar(255) DEFAULT NULL, 11 | PRIMARY KEY (id) 12 | ); 13 | 14 | 15 | CREATE TABLE app_user ( 16 | id bigint NOT NULL , 17 | first_name varchar(255) NOT NULL, 18 | last_name varchar(255) NOT NULL, 19 | password varchar(255) NOT NULL, 20 | username varchar(255) NOT NULL, 21 | PRIMARY KEY (id) 22 | ); 23 | 24 | CREATE TABLE user_role ( 25 | user_id bigint NOT NULL, 26 | role_id bigint NOT NULL, 27 | CONSTRAINT FK859n2jvi8ivhui0rl0esws6o FOREIGN KEY (user_id) REFERENCES app_user (id), 28 | CONSTRAINT FKa68196081fvovjhkek5m97n3y FOREIGN KEY (role_id) REFERENCES app_role (id) 29 | ); 30 | 31 | drop table if exists oauth_client_details; 32 | create table oauth_client_details ( 33 | client_id VARCHAR(256) PRIMARY KEY, 34 | resource_ids VARCHAR(256), 35 | client_secret VARCHAR(256), 36 | scope VARCHAR(256), 37 | authorized_grant_types VARCHAR(256), 38 | web_server_redirect_uri VARCHAR(256), 39 | authorities VARCHAR(256), 40 | access_token_validity INTEGER, 41 | refresh_token_validity INTEGER, 42 | additional_information VARCHAR(4096), 43 | autoapprove VARCHAR(256) 44 | ); 45 | 46 | drop table if exists oauth_client_token; 47 | create table oauth_client_token ( 48 | token_id VARCHAR(255), 49 | token bytea , 50 | authentication_id VARCHAR(255), 51 | user_name VARCHAR(255), 52 | client_id VARCHAR(255) 53 | ); 54 | 55 | drop table if exists oauth_access_token; 56 | create table oauth_access_token ( 57 | token_id VARCHAR(255), 58 | token bytea, 59 | authentication_id VARCHAR(255), 60 | user_name VARCHAR(255), 61 | client_id VARCHAR(255), 62 | authentication bytea, 63 | refresh_token VARCHAR(255) 64 | ); 65 | 66 | create index oauth_access_token_id on oauth_access_token(token_id); 67 | create index oauth_refresh_token_id on oauth_access_token(token_id); 68 | 69 | drop table if exists oauth_refresh_token; 70 | create table oauth_refresh_token ( 71 | token_id VARCHAR(255), 72 | token bytea, 73 | authentication bytea 74 | ); 75 | drop table if exists oauth_code; 76 | create table oauth_code ( 77 | code VARCHAR(255), authentication bytea 78 | ); 79 | 80 | drop table if exists oauth_approvals; 81 | create table oauth_approvals ( 82 | userId VARCHAR(255), 83 | clientId VARCHAR(255), 84 | scope VARCHAR(255), 85 | status VARCHAR(10), 86 | expiresAt TIMESTAMP, 87 | lastModifiedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP 88 | ); 89 | drop table if exists ClientDetails; 90 | create table ClientDetails ( 91 | appId VARCHAR(255) PRIMARY KEY, 92 | resourceIds VARCHAR(255), 93 | appSecret VARCHAR(255), 94 | scope VARCHAR(255), 95 | grantTypes VARCHAR(255), 96 | redirectUrl VARCHAR(255), 97 | authorities VARCHAR(255), 98 | access_token_validity INTEGER, 99 | refresh_token_validity INTEGER, 100 | additionalInformation VARCHAR(4096), 101 | autoApproveScopes VARCHAR(255) 102 | ); 103 | 104 | INSERT INTO app_role (id, role_name, description) VALUES (1, 'STANDARD_USER', 'Standard User - Has no admin rights'); 105 | INSERT INTO app_role (id, role_name, description) VALUES (2, 'ADMIN_USER', 'Admin User - Has permission to perform admin tasks'); 106 | 107 | -- USER john.doe 108 | -- non-encrypted password: jwtpass 109 | INSERT INTO app_user (id, first_name, last_name, password, username) VALUES (1, 'John', 'Doe', '$2a$10$qtH0F1m488673KwgAfFXEOWxsoZSeHqqlB/8BTt3a6gsI5c2mdlfe', 'john.doe'); 110 | INSERT INTO app_user (id, first_name, last_name, password, username) VALUES (2, 'Admin', 'Admin', '$2a$10$qtH0F1m488673KwgAfFXEOWxsoZSeHqqlB/8BTt3a6gsI5c2mdlfe', 'admin.admin'); 111 | 112 | 113 | INSERT INTO user_role(user_id, role_id) VALUES(1,1); 114 | INSERT INTO user_role(user_id, role_id) VALUES(2,1); 115 | INSERT INTO user_role(user_id, role_id) VALUES(2,2); 116 | 117 | -- Populate random city table 118 | 119 | INSERT INTO random_city(id, name) VALUES (1, 'Bamako'); 120 | INSERT INTO random_city(id, name) VALUES (2, 'Nonkon'); 121 | INSERT INTO random_city(id, name) VALUES (3, 'Houston'); 122 | INSERT INTO random_city(id, name) VALUES (4, 'Toronto'); 123 | INSERT INTO random_city(id, name) VALUES (5, 'New York City'); 124 | INSERT INTO random_city(id, name) VALUES (6, 'Mopti'); 125 | 126 | -- insert client details 127 | INSERT INTO oauth_client_details 128 | (client_id, client_secret, scope, authorized_grant_types, 129 | authorities, access_token_validity, refresh_token_validity) 130 | VALUES 131 | ('testjwtclientid', 'XY7kmzoNzl100', 'read,write', 'password,refresh_token,client_credentials,authorization_code', 'ROLE_CLIENT,ROLE_TRUSTED_CLIENT', 900, 2592000); 132 | INSERT INTO oauth_client_details 133 | (client_id, client_secret, scope, authorized_grant_types, 134 | authorities, access_token_validity, refresh_token_validity) 135 | VALUES 136 | ('ANDROID_APP', 'VzagO+ufWljKCWHfY1a+0uR8Vek=', 'read,write', 'password,refresh_token,client_credentials,authorization_code', 'ROLE_CLIENT,ROLE_TRUSTED_CLIENT', 6048800, 259200000); 137 | ALTER TABLE oauth_access_token ADD CONSTRAINT user_client_const UNIQUE (user_name,client_id); 138 | 139 | ---LDAP username ben Password benspassword -------------------------------------------------------------------------------- /src/main/resources/ldap-data.ldif: -------------------------------------------------------------------------------- 1 | dn: dc=springframework,dc=org 2 | objectclass: top 3 | objectclass: domain 4 | objectclass: extensibleObject 5 | dc: springframework 6 | 7 | dn: ou=groups,dc=springframework,dc=org 8 | objectclass: top 9 | objectclass: organizationalUnit 10 | ou: groups 11 | 12 | dn: ou=subgroups,ou=groups,dc=springframework,dc=org 13 | objectclass: top 14 | objectclass: organizationalUnit 15 | ou: subgroups 16 | 17 | dn: ou=people,dc=springframework,dc=org 18 | objectclass: top 19 | objectclass: organizationalUnit 20 | ou: people 21 | 22 | dn: ou=space cadets,dc=springframework,dc=org 23 | objectclass: top 24 | objectclass: organizationalUnit 25 | ou: space cadets 26 | 27 | dn: ou=\"quoted people\",dc=springframework,dc=org 28 | objectclass: top 29 | objectclass: organizationalUnit 30 | ou: "quoted people" 31 | 32 | dn: ou=otherpeople,dc=springframework,dc=org 33 | objectclass: top 34 | objectclass: organizationalUnit 35 | ou: otherpeople 36 | 37 | dn: uid=ben,ou=people,dc=springframework,dc=org 38 | objectclass: top 39 | objectclass: person 40 | objectclass: organizationalPerson 41 | objectclass: inetOrgPerson 42 | cn: Ben Alex 43 | sn: Alex 44 | uid: ben 45 | userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ= 46 | 47 | dn: uid=bob,ou=people,dc=springframework,dc=org 48 | objectclass: top 49 | objectclass: person 50 | objectclass: organizationalPerson 51 | objectclass: inetOrgPerson 52 | cn: Bob Hamilton 53 | sn: Hamilton 54 | uid: bob 55 | userPassword: bobspassword 56 | 57 | dn: uid=joe,ou=otherpeople,dc=springframework,dc=org 58 | objectclass: top 59 | objectclass: person 60 | objectclass: organizationalPerson 61 | objectclass: inetOrgPerson 62 | cn: Joe Smeth 63 | sn: Smeth 64 | uid: joe 65 | userPassword: joespassword 66 | 67 | dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org 68 | objectclass: top 69 | objectclass: person 70 | objectclass: organizationalPerson 71 | objectclass: inetOrgPerson 72 | cn: Mouse, Jerry 73 | sn: Mouse 74 | uid: jerry 75 | userPassword: jerryspassword 76 | 77 | dn: cn=slash/guy,ou=people,dc=springframework,dc=org 78 | objectclass: top 79 | objectclass: person 80 | objectclass: organizationalPerson 81 | objectclass: inetOrgPerson 82 | cn: slash/guy 83 | sn: Slash 84 | uid: slashguy 85 | userPassword: slashguyspassword 86 | 87 | dn: cn=quote\"guy,ou=\"quoted people\",dc=springframework,dc=org 88 | objectclass: top 89 | objectclass: person 90 | objectclass: organizationalPerson 91 | objectclass: inetOrgPerson 92 | cn: quote\"guy 93 | sn: Quote 94 | uid: quoteguy 95 | userPassword: quoteguyspassword 96 | 97 | dn: uid=space cadet,ou=space cadets,dc=springframework,dc=org 98 | objectclass: top 99 | objectclass: person 100 | objectclass: organizationalPerson 101 | objectclass: inetOrgPerson 102 | cn: Space Cadet 103 | sn: Cadet 104 | uid: space cadet 105 | userPassword: spacecadetspassword 106 | 107 | 108 | 109 | dn: cn=developers,ou=groups,dc=springframework,dc=org 110 | objectclass: top 111 | objectclass: groupOfUniqueNames 112 | cn: developers 113 | ou: developer 114 | uniqueMember: uid=ben,ou=people,dc=springframework,dc=org 115 | uniqueMember: uid=bob,ou=people,dc=springframework,dc=org 116 | 117 | dn: cn=managers,ou=groups,dc=springframework,dc=org 118 | objectclass: top 119 | objectclass: groupOfUniqueNames 120 | cn: managers 121 | ou: manager 122 | uniqueMember: uid=ben,ou=people,dc=springframework,dc=org 123 | uniqueMember: cn=mouse\, jerry,ou=people,dc=springframework,dc=org 124 | 125 | dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org 126 | objectclass: top 127 | objectclass: groupOfUniqueNames 128 | cn: submanagers 129 | ou: submanager 130 | uniqueMember: uid=ben,ou=people,dc=springframework,dc=org --------------------------------------------------------------------------------