Wednesday, January 25, 2023

Spring Authorization Server 1.0 with LDAP - Spring Security 6

 

Introduction:


Spring Authorization Server is a framework that provides implementations of the OAuth 2.1 and OpenID Connect 1.0 specifications and other related specifications. It is built on top of Spring Security to provide a secure, light-weight, and customizable foundation for building OpenID Connect 1.0 Identity Providers and OAuth2 Authorization Server products.

Spring Authorization Server requires a Java 17 or higher Runtime Environment. 


Demo


In this demo, I integrated Spring Authorization Server 1.0 with LDAP.  In LDAP servers can use LDIF (LDAP Data Interchange Format) files to exchange user data. so I used test-server.ldif provide by spring example.



Technology


  • Spring Boot 3.0.1
  • Java 17
  • Spring Authorization Server 1.0
  • Maven 
  • IntelliJ IDEA


Configuration Spring Boot project 


Add Spring Authorization Server and  Spring Security as a dependencies, as in the following example:

   <dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>1.0.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>



application.yml



The spring.ldap.embedded.ldif property inside application.yml lets Spring Boot pull in an LDIF data file. 
server:
port: 9000

spring:
ldap:
embedded:
base-dn: dc=springframework,dc=org
ldif: classpath:test-server.ldif
port: 8389
url: ldap://localhost:8389/

logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
# org.springframework.boot.autoconfigure: DEBUG

Create a Simples Webs Controllers

@RestController
public class HelloResource {

@GetMapping("/hello")
public String hello(Principal principal) {
return "Hello "+principal.getName();
}
}

@Controller
public class AuthorizationController {

@GetMapping(value={"", "/", "landing"})
public String consent(Principal principal, Model model){

System.out.println("user "+principal.getName());
model.addAttribute("principalName", principal.getName());
return "landing";
}
}


Set up Spring Security


The ldapAuthentication() method configures things so that the user name at the login form is plugged into {0} such that it searches uid={0},ou=people,dc=springframework,dc=org in the LDAP server. Also, the passwordCompare() method configures the encoder and the name of the password’s attribute.

@Configuration
public class DefaultSecurityConfig {

@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());

return http.build();
}

/* @Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withUsername("henry")
.password(passwordEncoder().encode("123"))
.roles("USER")
.build();

return new InMemoryUserDetailsManager(userDetails);
}*/
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword");
}
}

Set up Authorization Server


  • To get started, You need the minimum required components defined as a @Bean in a Spring @Configuration
  • The first bean is used to define the OAuth2 Protocol Endpoint.
  • The RegisteredClientRepository is the central component where new clients can be registered and existing clients can be queried. It is used by other components when following a specific protocol flow, such as client authentication, authorization grant processing, token introspection, dynamic client registration, and others.
  • com.nimbusds.jose.jwk.source.JWKSource for signing access tokens.
  • java.security.KeyPair with keys generated on startup used to create the JWKSource.
  • JwtDecoder for decoding signed access tokens.
  • AuthorizationServerSettings contains the configuration settings for the OAuth2 authorization server. It specifies the URI for the protocol endpoints as well as the issuer identifier. The default URI for the protocol endpoints are as follows:

@Configuration
public class AuthorizationServerConfig {

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

return http.build();
}

@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:9000/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:9000/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();

return new InMemoryRegisteredClientRepository(registeredClient);
}

@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}

private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}

@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}

@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

 

Run & Test


Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.

http://localhost:9000/landing





User: henry
Password: 123

Result this:


















Source Code


Here on GitHub.




References.

https://spring.io/projects/spring-authorization-server
https://spring.io/guides/gs/authenticating-ldap/
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
https://spring.io/guides/gs/authenticating-ldap/

















No comments:

Post a Comment

Automating Deployments with CronJobs in Google Kubernetes Engine (GKE)

In the realm of container orchestration, automation plays a pivotal role in streamlining operations. Google Kubernetes Engine (GKE) offers r...