---
title: Notes on Logging in with IAM Identity Center (formerly AWS SSO) and SAML2 Integration in Spring Boot
tags: ["Java", "Spring Boot", "Spring Security", "SAML", "OpenSSL", "IAM Identity Center", "AWS"]
categories: ["Programming", "Java", "org", "springframework", "security", "saml2"]
date: 2024-09-04T08:50:20Z
updated: 2024-09-04T10:45:12Z
---

This is a note on how to log in to Spring Boot using users managed by IAM Identity Center (formerly AWS SSO).

IAM Identity Center does not have OIDC Provider functionality, but it can be used as a SAML2 Identity Provider. Therefore, we will implement login using AWS users through SAML2 integration in Spring Boot.

SAML2 integration is provided by Spring Security.  
https://docs.spring.io/spring-security/reference/servlet/saml2/index.html

**Table of Contents**  
<!-- toc -->

### Creating a Template Project

First, create a template project using Spring Initializr.

```bash
curl -s https://start.spring.io/starter.tgz \
       -d artifactId=hello-saml \
       -d baseDir=hello-saml \
       -d packageName=com.example \
       -d dependencies=web,actuator,security,configuration-processor \
       -d type=maven-project \
       -d name=hello-saml \
       -d applicationName=HelloSamlApplication | tar -xzvf -
cd hello-saml
```

To use SAML2 integration, add the following dependency to `pom.xml`.

```xml
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-saml2-service-provider</artifactId>
		</dependency>
```

The library OpenSAML used by `spring-security-saml2-service-provider` is not available in Maven Central due to [various reasons](https://shibboleth.atlassian.net/wiki/spaces/DEV/pages/1123844333/Use+of+Maven+Central), so we will add the following Maven repository.

```xml
<project>
	<!-- ... -->
	<repositories>
		<repository>
			<id>shibboleth</id>
			<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
		</repository>
	</repositories>
	<!-- ... -->
</project>
```

Next, we will write the configuration for logging in with SAML2 in `SecurityConfig`.

```java
cat <<'EOF' > src/main/java/com/example/SecurityConfig.java
package com.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class SecurityConfig {
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		return http
				.authorizeHttpRequests(authz -> authz
						.requestMatchers("/error").permitAll()
						.anyRequest().authenticated())
				.saml2Login(withDefaults())
				.saml2Metadata(withDefaults())
				.build();
	}
}
EOF
```

Create a Controller to display user information authenticated via SAML2.

```java
cat <<'EOF' > src/main/java/com/example/HelloController.java
package com.example;

import java.util.Map;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	@GetMapping(path = "/")
	public Map<String, Object> hello(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
		return Map.of("username", principal.getName(), "attributes", principal.getAttributes());
	}
}
EOF
```

### Configuring the Identity Provider (Relying Party)

Next, we will configure the Identity Provider (Relying Party) in IAM Identity Center.

First, register the application by clicking the "Add application" button on the "Applications" screen.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/c0135227-fb39-4af4-9a00-e6823b5ebc39">

Select "I have an application I want to set up".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/f81f2802-7306-473b-9d1c-9c77e7777a51">

Choose "Application type" as "SAML 2.0".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/f880b4d6-0cf0-4e31-bdd1-e35bb0dda5f7">

Set the "Display name" to "Hello SAML".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/8ecaffa3-e9d3-41d0-b0b8-7174bd851b3f">

At this point, copy the URL of "IAM Identity Center SAML metadata file" under "IAM Identity Center metadata".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/91ac5f11-d040-4f62-8227-49f180cf16a4">

Set this URL in `application.properties` as follows:

```properties
METADATA_URL=https://portal.sso.****.amazonaws.com/saml/metadata/****
cat <<EOF > src/main/resources/application.properties
spring.application.name=hello-saml
spring.security.saml2.relyingparty.registration.awssso.entity-id=hello-saml
spring.security.saml2.relyingparty.registration.awssso.assertingparty.metadata-uri=$METADATA_URL
EOF
```

Once the `application.properties` is configured, start the application.

```bash
./mvnw spring-boot:run
```

The metadata for the Service Provider will be published at `/saml2/metadata/<registrationId>`, so download it.

```bash
curl -s http://localhost:8080/saml2/metadata/awssso -o ~/Downloads/metadata-awssso.xml
```

Upload the downloaded metadata in "Application metadata" and click "Submit".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/c491cde6-6a57-46f2-9fe4-6864c4288519">

Once the application is created and the "Status" is "Active", you are good to go.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/16683266-4415-402e-9fd2-795f904a3c46">

Next, assign users and groups to this application by clicking the "Assign users and groups" button under "Assigned users and groups".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/aa932676-5834-4513-8af4-58dcebcbad10">

If there are no users or groups, create them. Here, I assigned the `developers` group and the `administrators` group.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/ce0f387a-64b6-4ed7-b085-e4f6ece30391">

Next, perform the attribute mapping. Click "Actions" in the application's "Details" and select "Edit attribute mappings".

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/a78e4d85-d9ef-4e51-a5e0-1eb0ff5bf458">

Refer to https://docs.aws.amazon.com/singlesignon/latest/userguide/attributemappingsconcept.html for mapping.

Here, I mapped as follows:

* `Subject` (used as username) ... `${user.email}` (type `emailAddress`)
* `firstName` ... `${user.givenName}` (type `unspecified`)
* `lastName` ... `${user.familyName}` (type `unspecified`)
* `groups` ... `${user.groups}` (type `unspecified`)

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/94237934-6bd7-4e42-a5ea-81da17bf454c">

After clicking "Save changes" to save the settings, access the application you started earlier (http://localhost:8080).

You will be redirected to the IAM Identity Center login screen.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/ebc4493f-00ab-46ce-a6bf-37f409a4027b">

After logging into IAM Identity Center, you will be redirected again.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/40538bfb-613f-42ae-b51c-4fb56ca33553">

You will be redirected to http://localhost:8080, and the logged-in user information will be displayed.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/bbcd62f1-f478-4250-ba98-de3f6e2eacc3">

The information about the groups the user belongs to is included in the `groups` attribute, but you will receive the ID instead of the name.

You can check the Group ID in the details screen of the IAM Identity Center group.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/2441a59d-74c4-4e97-8d7b-48cb4cfca3a0">

Now you can log in to the Spring Boot application using IAM Identity Center users with SAML2.

### Single Logout

Next, we will configure single logout.

Add the following configuration to `SecurityConfig`.

```java
@Configuration(proxyBeanMethods = false)
public class SecurityConfig {
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		return http
				.authorizeHttpRequests(authz -> authz
						.requestMatchers("/error").permitAll()
						.anyRequest().authenticated())
				.saml2Login(withDefaults())
				.saml2Logout(withDefaults()) // <---
				.saml2Metadata(withDefaults())
				.build();
	}
}
```

Single logout requests require signing. Set the private and public keys with the following command.

```bash
openssl req -x509 -newkey rsa:4096 -keyout src/main/resources/key.pem -out src/main/resources/cert.pem -sha256 -days 3650 -nodes -subj "/CN=@making/O=LOL.MAKI/C=JP"
```

Append the following settings to `application.properties`.

```properties
cat <<'EOF' >> src/main/resources/application.properties
spring.security.saml2.relyingparty.registration.awssso.signing.credentials[0].certificate-location=classpath:cert.pem
spring.security.saml2.relyingparty.registration.awssso.signing.credentials[0].private-key-location.=classpath:key.pem
spring.security.saml2.relyingparty.registration.awssso.singlelogout.binding=post
spring.security.saml2.relyingparty.registration.awssso.singlelogout.response-url={baseUrl}/logout/saml2/slo/{registrationId}
EOF
```

Stop and restart the application.

```bash
./mvnw spring-boot:run
```

After accessing http://localhost:8080 once, go to http://localhost:8080/logout.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/1b4c36ac-011a-4967-b1e9-6ee75f540a00">

Click the "Log Out" button.

This will log you out of the application, but you will not be logged out of IAM Identity Center.  
You will be redirected to the portal screen after logging into IAM Identity Center.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/5e6740de-5eb1-4efd-835b-a715f946c01f">

Normally, this setting should allow logout from the Identity Provider, but it did not work with IAM Identity Center.

I found the following statement, so single logout may not be supported in IAM Identity Center.  
https://github.com/aws-mwaa/upstream-to-airflow/blob/0a816c6f0b500e1b0515452e38e3446412f3e8e3/airflow/providers/amazon/aws/auth_manager/views/auth.py#L105

In fact, single logout worked with the same settings for [Microsoft Entra ID (formerly Azure AD)](/entries/820).

This may be a limitation of IAM Identity Center, so I will leave it as is.

### Authorization by Attributes

Users logged in via SAML2 have the `ROLE_USER` authority by default.  
The [documentation](https://docs.spring.io/spring-security/reference/servlet/saml2/login/authentication.html) describes how to customize the created user information.  
Using this method, you can add any authority to the user.

However, it is more convenient to directly set authorization using the attributes held by the user information, so we will implement the following `AuthorizationManager`.

```java
cat <<'EOF' > src/main/java/com/example/SamlAuthorizationManager.java
package com.example;

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;

public class SamlAuthorizationManager<T> implements AuthorizationManager<RequestAuthorizationContext> {

	private final Function<Saml2AuthenticatedPrincipal, T> applyer;

	private final Predicate<T> predicate;

	public SamlAuthorizationManager(Function<Saml2AuthenticatedPrincipal, T> applyer, Predicate<T> predicate) {
		this.applyer = applyer;
		this.predicate = predicate;
	}

	public static SamlAuthorizationManager<List<String>> attribute(String name, Predicate<List<String>> predicate) {
		return new SamlAuthorizationManager<>(principal -> principal.getAttribute(name), predicate);
	}

	public static SamlAuthorizationManager<String> firstAttribute(String name, Predicate<String> predicate) {
		return new SamlAuthorizationManager<>(principal -> principal.getFirstAttribute(name), predicate);
	}

	public static SamlAuthorizationManager<String> username(Predicate<String> predicate) {
		return new SamlAuthorizationManager<>(AuthenticatedPrincipal::getName, predicate);
	}

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
		Authentication auth = authentication.get();
		if (auth instanceof Saml2Authentication && auth.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) {
			T target = this.applyer.apply(principal);
			if (target == null) {
				return new AuthorizationDecision(false);
			}
			return new AuthorizationDecision(this.predicate.test(target));
		}
		else {
			return new AuthorizationDecision(false);
		}
	}
}
EOF
```

For example, to access the `/admin` path, the user must meet one of the following conditions:

* Include `a7443a38-70d1-709f-aa2c-4841adf65ed1` in the `groups` attribute
* The `email` attribute (only the first element) ends with `@example.com`
* The username is `admin` or `makingx@gmail.com`

These conditions can be defined using `SamlAuthorizationManager` as follows.

```java
// ...
import static com.example.SamlAuthorizationManager.attribute;
import static com.example.SamlAuthorizationManager.firstAttribute;
import static com.example.SamlAuthorizationManager.username;
import static org.springframework.security.authorization.AuthorizationManagers.anyOf;
// ...

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	return http
			.authorizeHttpRequests(authz -> authz
					.requestMatchers("/error").permitAll()
					// <---
					.requestMatchers("/admin").access(anyOf(
							attribute("groups", groups -> groups.contains("a7443a38-70d1-709f-aa2c-4841adf65ed1")),
							firstAttribute("email", email -> email.endsWith("@example.com")),
							username(username -> username.equals("admin") || username.equals("makingx@gmail.com"))))
					// --->
					.anyRequest().authenticated())
			.saml2Login(withDefaults())
			.saml2Metadata(withDefaults())
			.saml2Logout(withDefaults())
			.build();
}
```

Create a Controller for the `/admin` path.

```java
cat <<'EOF' > src/main/java/com/example/AdminController.java
package com.example;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AdminController {
	@GetMapping(path = "/admin")
	public String admin() {
		return "admin page";
	}
}
EOF
```

Restart the application and access http://localhost:8080/admin.

If you log in with an authorized user, the page will be displayed as follows.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/3cd16f67-1598-478d-907a-f5e52c32f26a">

If you log in with an unauthorized user, a 403 error will be displayed.

<img width="1024" alt="image" src="https://github.com/user-attachments/assets/eeeb3605-2fd0-44c0-add9-d12f01299652">

---

By integrating IAM Identity Center with SAML2 in Spring Boot, we were able to log in to the application using AWS users.  
Although single logout did not work, it is a convenient method if you are already managing users in IAM Identity Center and do not want to duplicate user management.
