Shibboleth IdP is an open-source SAML-based identity provider that enables single sign-on (SSO) for web applications. This guide covers Java-based implementation, customization, and integration.
Understanding Shibboleth IdP Architecture
Key Components:
- SAML Service Provider (SP): Applications requesting authentication
- SAML Identity Provider (IdP): Shibboleth server providing authentication
- Metadata: XML configuration describing SPs and IdPs
- Attribute Release: User information shared with SPs
- User Store: Backend authentication sources (LDAP, DB, etc.)
Prerequisites and Installation
1. System Requirements
# Java 8 or higher java -version # Apache Tomcat 9+ CATALINA_HOME=/opt/tomcat # Shibboleth IdP 4.x wget https://shibboleth.net/downloads/identity-provider/latest/shibboleth-identity-provider-4.3.1.tar.gz
2. Installation Steps
# Extract Shibboleth IdP tar -xzf shibboleth-identity-provider-4.3.1.tar.gz cd shibboleth-identity-provider-4.3.1 # Run installation ./bin/install.sh # Installation prompts: # - IdP Home: /opt/shibboleth-idp # - Hostname: idp.example.com # - Keystore password: changeit
3. Tomcat Configuration
<!-- /opt/tomcat/conf/server.xml --> <Context docBase="/opt/shibboleth-idp/war/idp.war" privileged="true" antiResourceLocking="false" antiJARLocking="false" unpackWAR="false" swallowOutput="true" />
Core Configuration Files
1. idp.properties
# /opt/shibboleth-idp/conf/idp.properties
idp.entityID = https://idp.example.com/idp/shibboleth
idp.scope = example.com
idp.sealer.storePassword = changeit
idp.sealer.keyPassword = changeit
idp.signing.key = %{idp.home}/credentials/idp-signing.key
idp.signing.cert = %{idp.home}/credentials/idp-signing.crt
idp.encryption.key = %{idp.home}/credentials/idp-encryption.key
idp.encryption.cert = %{idp.home}/credentials/idp-encryption.crt
2. ldap.properties
# /opt/shibboleth-idp/conf/ldap.properties
idp.authn.LDAP.ldapURL = ldap://ldap.example.com:389
idp.authn.LDAP.baseDN = ou=people,dc=example,dc=com
idp.authn.LDAP.bindDN = cn=admin,dc=example,dc=com
idp.authn.LDAP.bindDNCredential = password
idp.authn.LDAP.userFilter = (uid={user})
3. attribute-resolver.xml
<!-- /opt/shibboleth-idp/conf/attribute-resolver.xml -->
<AttributeResolver xmlns="urn:mace:shibboleth:2.0:resolver"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:pc="urn:mace:shibboleth:2.0:resolver:pc"
xmlns:ad="urn:mace:shibboleth:2.0:resolver:ad"
xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver ...">
<!-- LDAP Data Connector -->
<DataConnector id="myLDAP" xsi:type="dc:LDAPDirectory"
ldapURL="%{idp.authn.LDAP.ldapURL}"
baseDN="%{idp.authn.LDAP.baseDN}"
principal="%{idp.authn.LDAP.bindDN}"
principalCredential="%{idp.authn.LDAP.bindDNCredential}">
<FilterTemplate>
<![CDATA[
%{idp.authn.LDAP.userFilter}
]]>
</FilterTemplate>
</DataConnector>
<!-- Attribute Definitions -->
<AttributeDefinition id="uid" xsi:type="ad:Simple">
<InputDataConnector ref="myLDAP" attributeNames="uid" />
<AttributeEncoder xsi:type="enc:SAML1String" name="urn:mace:dir:attribute-def:uid" />
<AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1"
friendlyName="uid" />
</AttributeDefinition>
<AttributeDefinition id="mail" xsi:type="ad:Simple">
<InputDataConnector ref="myLDAP" attributeNames="mail" />
<AttributeEncoder xsi:type="enc:SAML1String" name="urn:mace:dir:attribute-def:mail" />
<AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:0.9.2342.19200300.100.1.3"
friendlyName="mail" />
</AttributeDefinition>
<AttributeDefinition id="displayName" xsi:type="ad:Simple">
<InputDataConnector ref="myLDAP" attributeNames="displayName" />
<AttributeEncoder xsi:type="enc:SAML1String" name="urn:mace:dir:attribute-def:displayName" />
<AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:2.16.840.1.113730.3.1.241"
friendlyName="displayName" />
</AttributeDefinition>
</AttributeResolver>
4. attribute-filter.xml
<!-- /opt/shibboleth-idp/conf/attribute-filter.xml --> <AttributeFilterPolicy id="examplePolicy"> <PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="https://sp.example.com/shibboleth" /> <AttributeRule attributeID="uid"> <PermitValueRule xsi:type="basic:ANY" /> </AttributeRule> <AttributeRule attributeID="mail"> <PermitValueRule xsi:type="basic:ANY" /> </AttributeRule> <AttributeRule attributeID="displayName"> <PermitValueRule xsi:type="basic:ANY" /> </AttributeRule> </AttributeFilterPolicy>
Java Customization and Extensions
1. Custom Authentication Flow
// CustomUsernamePasswordAuth.java
package com.example.idp.auth;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import net.shibboleth.idp.authn.AbstractValidationAction;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.UsernamePasswordContext;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
/**
* Custom authentication validator integrating with existing user stores
*/
public class CustomUsernamePasswordAuth extends AbstractValidationAction {
private final Logger log = LoggerFactory.getLogger(CustomUsernamePasswordAuth.class);
@Autowired
private CustomUserService userService;
private UsernamePasswordContext upContext;
@Override
protected void doExecute(final ProfileRequestContext profileRequestContext,
final AuthenticationContext authenticationContext) {
try {
upContext = authenticationContext.getSubcontext(UsernamePasswordContext.class);
if (upContext == null) {
log.error("No username/password context available");
handleError(profileRequestContext, authenticationContext,
AuthnEventIds.INVALID_CREDENTIALS, "Invalid credentials");
return;
}
final String username = upContext.getUsername();
final String password = upContext.getPassword();
if (username == null || password == null) {
log.warn("Username or password is null");
handleError(profileRequestContext, authenticationContext,
AuthnEventIds.INVALID_CREDENTIALS, "Invalid credentials");
return;
}
// Custom authentication logic
if (authenticateUser(username, password)) {
log.info("Successful authentication for user: {}", username);
buildAuthenticationResult(profileRequestContext, authenticationContext);
} else {
log.warn("Authentication failed for user: {}", username);
handleError(profileRequestContext, authenticationContext,
AuthnEventIds.INVALID_CREDENTIALS, "Invalid credentials");
}
} catch (Exception e) {
log.error("Authentication error", e);
handleError(profileRequestContext, authenticationContext,
AuthnEventIds.AUTHN_EXCEPTION, "Authentication error");
}
}
private boolean authenticateUser(String username, String password) {
try {
return userService.authenticate(username, password);
} catch (Exception e) {
log.error("Authentication service error for user: {}", username, e);
return false;
}
}
}
// CustomUserService.java
package com.example.idp.auth;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class CustomUserService {
private Map<String, User> userStore;
@PostConstruct
public void init() {
userStore = new ConcurrentHashMap<>();
// Initialize with test users - replace with real user store
userStore.put("user1", new User("user1", "password123", "[email protected]"));
userStore.put("admin", new User("admin", "admin123", "[email protected]"));
}
public boolean authenticate(String username, String password) {
User user = userStore.get(username);
return user != null && user.getPassword().equals(password);
}
public User getUser(String username) {
return userStore.get(username);
}
public static class User {
private String username;
private String password;
private String email;
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
// Getters and setters
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getEmail() { return email; }
}
}
2. Custom Attribute Resolver
// CustomAttributeResolver.java
package com.example.idp.attribute;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.shibboleth.idp.attribute.IdPAttribute;
import net.shibboleth.idp.attribute.StringAttributeValue;
import net.shibboleth.idp.attribute.context.AttributeContext;
import net.shibboleth.idp.attribute.resolver.AbstractAttributeDefinition;
import net.shibboleth.idp.attribute.resolver.ResolutionException;
import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
import java.util.Collections;
import java.util.List;
/**
* Custom attribute definition for dynamic attribute resolution
*/
public class CustomAttributeResolver extends AbstractAttributeDefinition {
private final Logger log = LoggerFactory.getLogger(CustomAttributeResolver.class);
private CustomAttributeService attributeService;
public void setAttributeService(CustomAttributeService attributeService) {
this.attributeService = attributeService;
}
@Override
protected IdPAttribute doAttributeDefinitionResolve(
AttributeResolutionContext resolutionContext,
AttributeResolverWorkContext workContext) throws ResolutionException {
try {
String principal = resolutionContext.getPrincipal();
ProfileRequestContext profileRequestContext = resolutionContext.getProfileRequestContext();
log.debug("Resolving custom attributes for principal: {}", principal);
// Create attribute
IdPAttribute attribute = new IdPAttribute(getId());
// Get custom attributes from service
List<String> customAttributes = attributeService.getCustomAttributes(principal);
for (String attrValue : customAttributes) {
attribute.setValues(Collections.singleton(new StringAttributeValue(attrValue)));
}
log.info("Resolved {} custom attributes for principal: {}",
customAttributes.size(), principal);
return attribute;
} catch (Exception e) {
log.error("Error resolving custom attributes", e);
throw new ResolutionException("Failed to resolve custom attributes", e);
}
}
}
// CustomAttributeService.java
package com.example.idp.attribute;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class CustomAttributeService {
public List<String> getCustomAttributes(String username) {
// Implement your custom attribute logic here
// This could call a database, external API, etc.
if ("admin".equals(username)) {
return Arrays.asList("ROLE_ADMIN", "DEPARTMENT_IT", "SECURITY_HIGH");
} else {
return Arrays.asList("ROLE_USER", "DEPARTMENT_GENERAL", "SECURITY_MEDIUM");
}
}
public String getCustomAttribute(String username, String attributeName) {
// Implementation for specific attribute retrieval
return null;
}
}
3. Spring Configuration for Custom Components
<!-- /opt/shibboleth-idp/conf/global.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- Component scanning for custom classes --> <context:component-scan base-package="com.example.idp" /> <!-- Custom Authentication Flow --> <bean id="authn/CustomAuth" parent="shibboleth.AuthenticationFlow" p:passiveAuthenticationSupported="true" p:forcedAuthenticationSupported="true"> <property name="supportedPrincipals"> <list> <bean parent="shibboleth.SAML2AuthnContextClassRef" c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:Password" /> </list> </property> </bean> <!-- Custom Authentication Action --> <bean id="ValidateCustomAuth" class="com.example.idp.auth.CustomUsernamePasswordAuth" scope="prototype" p:userService-ref="customUserService" /> <!-- Custom Attribute Resolver --> <bean id="customAttributeDefinition" class="com.example.idp.attribute.CustomAttributeResolver" p:attributeService-ref="customAttributeService" p:dependencyOnly="false"> <property name="activationCondition"> <bean parent="shibboleth.Conditions.TRUE" /> </property> </bean> </beans>
4. REST API Integration for User Management
// UserManagementController.java
package com.example.idp.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.idp.auth.CustomUserService;
import java.util.Map;
@RestController
@RequestMapping("/api/admin")
public class UserManagementController {
private final Logger log = LoggerFactory.getLogger(UserManagementController.class);
@Autowired
private CustomUserService userService;
@GetMapping("/users/{username}")
public ResponseEntity<?> getUser(@PathVariable String username) {
try {
CustomUserService.User user = userService.getUser(username);
if (user == null) {
return ResponseEntity.notFound().build();
}
// Return user without password
return ResponseEntity.ok(Map.of(
"username", user.getUsername(),
"email", user.getEmail()
));
} catch (Exception e) {
log.error("Error retrieving user: {}", username, e);
return ResponseEntity.internalServerError().build();
}
}
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody Map<String, String> userData) {
try {
String username = userData.get("username");
String password = userData.get("password");
String email = userData.get("email");
// Implementation for user creation
log.info("Creating user: {}", username);
return ResponseEntity.ok().body(Map.of("status", "success", "username", username));
} catch (Exception e) {
log.error("Error creating user", e);
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
}
5. Monitoring and Health Check
// IdPHealthIndicator.java
package com.example.idp.health;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.io.File;
@Component
public class IdPHealthIndicator implements HealthIndicator {
private final Logger log = LoggerFactory.getLogger(IdPHealthIndicator.class);
@Override
public Health health() {
try {
// Check critical IdP files
if (!checkRequiredFiles()) {
return Health.down().withDetail("error", "Required files missing").build();
}
// Check metadata validity
if (!checkMetadata()) {
return Health.down().withDetail("error", "Metadata invalid").build();
}
return Health.up()
.withDetail("version", "4.3.1")
.withDetail("status", "operational")
.build();
} catch (Exception e) {
log.error("Health check failed", e);
return Health.down(e).build();
}
}
private boolean checkRequiredFiles() {
String idpHome = System.getenv("IDP_HOME");
if (idpHome == null) {
idpHome = "/opt/shibboleth-idp";
}
File[] requiredFiles = {
new File(idpHome + "/credentials/idp-signing.crt"),
new File(idpHome + "/credentials/idp-encryption.crt"),
new File(idpHome + "/metadata/idp-metadata.xml")
};
for (File file : requiredFiles) {
if (!file.exists()) {
log.error("Required file missing: {}", file.getAbsolutePath());
return false;
}
}
return true;
}
private boolean checkMetadata() {
// Implement metadata validation logic
return true;
}
}
Advanced Configuration
1. Database-Backed Session Storage
// DatabaseSessionManager.java
package com.example.idp.session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class DatabaseSessionManager {
private final Logger log = LoggerFactory.getLogger(DatabaseSessionManager.class);
private final JdbcTemplate jdbcTemplate;
private final Map<String, Session> sessionCache;
public DatabaseSessionManager(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.sessionCache = new ConcurrentHashMap<>();
}
@PostConstruct
public void init() {
createSessionTable();
}
private void createSessionTable() {
jdbcTemplate.execute("""
CREATE TABLE IF NOT EXISTS idp_sessions (
session_id VARCHAR(64) PRIMARY KEY,
principal VARCHAR(255) NOT NULL,
creation_time TIMESTAMP NOT NULL,
last_accessed TIMESTAMP NOT NULL,
attributes TEXT,
expiry_time TIMESTAMP NOT NULL
)
""");
}
public void storeSession(String sessionId, Session session) {
String sql = """
INSERT INTO idp_sessions (session_id, principal, creation_time, last_accessed, attributes, expiry_time)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
last_accessed = VALUES(last_accessed),
attributes = VALUES(attributes),
expiry_time = VALUES(expiry_time)
""";
jdbcTemplate.update(sql,
sessionId,
session.getPrincipal(),
Timestamp.valueOf(session.getCreationTime()),
Timestamp.valueOf(LocalDateTime.now()),
serializeAttributes(session.getAttributes()),
Timestamp.valueOf(session.getExpiryTime()));
sessionCache.put(sessionId, session);
}
public Session getSession(String sessionId) {
// Check cache first
Session cached = sessionCache.get(sessionId);
if (cached != null) {
return cached;
}
// Query database
String sql = "SELECT * FROM idp_sessions WHERE session_id = ? AND expiry_time > ?";
return jdbcTemplate.query(sql, rs -> {
if (rs.next()) {
Session session = new Session(
rs.getString("principal"),
rs.getTimestamp("creation_time").toLocalDateTime(),
rs.getTimestamp("expiry_time").toLocalDateTime()
);
session.setAttributes(deserializeAttributes(rs.getString("attributes")));
sessionCache.put(sessionId, session);
return session;
}
return null;
}, sessionId, Timestamp.valueOf(LocalDateTime.now()));
}
@Scheduled(fixedRate = 300000) // Run every 5 minutes
public void cleanupExpiredSessions() {
int deleted = jdbcTemplate.update(
"DELETE FROM idp_sessions WHERE expiry_time < ?",
Timestamp.valueOf(LocalDateTime.now()));
log.info("Cleaned up {} expired sessions", deleted);
// Also clear cache
sessionCache.entrySet().removeIf(entry ->
entry.getValue().getExpiryTime().isBefore(LocalDateTime.now()));
}
private String serializeAttributes(Map<String, Object> attributes) {
// Implement JSON serialization
return "{}"; // Simplified
}
private Map<String, Object> deserializeAttributes(String data) {
// Implement JSON deserialization
return Map.of(); // Simplified
}
public static class Session {
private String principal;
private LocalDateTime creationTime;
private LocalDateTime expiryTime;
private Map<String, Object> attributes;
public Session(String principal, LocalDateTime creationTime, LocalDateTime expiryTime) {
this.principal = principal;
this.creationTime = creationTime;
this.expiryTime = expiryTime;
}
// Getters and setters
public String getPrincipal() { return principal; }
public LocalDateTime getCreationTime() { return creationTime; }
public LocalDateTime getExpiryTime() { return expiryTime; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttributes(Map<String, Object> attributes) { this.attributes = attributes; }
}
}
2. Custom Logging Configuration
// AuditLogger.java
package com.example.idp.audit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Map;
@Component
public class AuditLogger {
private final Logger auditLog = LoggerFactory.getLogger("AUDIT");
public void logAuthenticationSuccess(String username, HttpServletRequest request) {
auditLog.info("AUTH_SUCCESS|{}|{}|{}|{}",
LocalDateTime.now(),
username,
request.getRemoteAddr(),
getSessionId(request));
}
public void logAuthenticationFailure(String username, String reason, HttpServletRequest request) {
auditLog.warn("AUTH_FAILURE|{}|{}|{}|{}|{}",
LocalDateTime.now(),
username,
request.getRemoteAddr(),
reason,
getSessionId(request));
}
public void logAttributeRelease(String username, String spEntityId, Map<String, String> attributes) {
auditLog.info("ATTRIBUTE_RELEASE|{}|{}|{}|{}",
LocalDateTime.now(),
username,
spEntityId,
String.join(",", attributes.keySet()));
}
private String getSessionId(HttpServletRequest request) {
return request.getSession(false) != null ?
request.getSession().getId() : "no-session";
}
}
Testing and Validation
1. Unit Tests
// CustomAuthenticationTest.java
package com.example.idp.auth;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class CustomAuthenticationTest {
@Mock
private CustomUserService userService;
@InjectMocks
private CustomUsernamePasswordAuth authAction;
@Test
void testSuccessfulAuthentication() {
// Setup
when(userService.authenticate("user1", "password123")).thenReturn(true);
ProfileRequestContext profileContext = new ProfileRequestContext();
AuthenticationContext authContext = new AuthenticationContext();
// TODO: Setup username/password context
// Execute
// authAction.doExecute(profileContext, authContext);
// Verify
// assertTrue(authContext.getAuthenticationResult().isSuccess());
}
}
2. Integration Test Configuration
// IdPIntegrationTest.java
package com.example.idp.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class IdPIntegrationTest {
@LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
@Test
void testIdPMetadataEndpoint() {
ResponseEntity<String> response = restTemplate.getForEntity(
"http://localhost:" + port + "/idp/profile/admin/metrics", String.class);
assertTrue(response.getStatusCode().is2xxSuccessful());
}
}
Deployment and Operations
1. Docker Configuration
# Dockerfile FROM tomcat:9-jre11 # Install Shibboleth IdP RUN wget -O /tmp/shibboleth.tar.gz \ https://shibboleth.net/downloads/identity-provider/latest/shibboleth-identity-provider-4.3.1.tar.gz RUN tar -xzf /tmp/shibboleth.tar.gz -C /tmp RUN /tmp/shibboleth-identity-provider-4.3.1/bin/install.sh \ -Didp.src.dir=/tmp/shibboleth-identity-provider-4.3.1 \ -Didp.target.dir=/opt/shibboleth-idp \ -Didp.merge.properties=/opt/shibboleth-idp/conf/idp.properties \ -Didp.sealer.password=changeit \ -Didp.keystore.password=changeit # Copy custom configuration COPY conf/ /opt/shibboleth-idp/conf/ COPY war/ /opt/shibboleth-idp/war/ # Expose ports EXPOSE 8080 8443 CMD ["catalina.sh", "run"]
2. Performance Monitoring
// IdPMetricsCollector.java
package com.example.idp.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class IdPMetricsCollector {
private final Counter authenticationSuccess;
private final Counter authenticationFailure;
private final Timer authenticationTimer;
public IdPMetricsCollector(MeterRegistry registry) {
this.authenticationSuccess = Counter.builder("idp.auth.success")
.description("Successful authentications")
.register(registry);
this.authenticationFailure = Counter.builder("idp.auth.failure")
.description("Failed authentications")
.register(registry);
this.authenticationTimer = Timer.builder("idp.auth.duration")
.description("Authentication request duration")
.register(registry);
}
public void recordAuthenticationSuccess(long duration) {
authenticationSuccess.increment();
authenticationTimer.record(duration, TimeUnit.MILLISECONDS);
}
public void recordAuthenticationFailure() {
authenticationFailure.increment();
}
}
Best Practices
- Security:
- Use strong cryptographic keys
- Regular security updates
- Proper attribute release policies
- Performance:
- Enable caching where appropriate
- Monitor session storage
- Optimize database queries
- Monitoring:
- Comprehensive logging
- Health checks
- Performance metrics
- Maintenance:
- Regular metadata updates
- Certificate rotation
- Backup configurations
Conclusion
Implementing Shibboleth IdP with Java customizations provides:
- Robust SAML-based SSO capabilities
- Flexible authentication mechanisms
- Custom attribute resolution for complex business logic
- Enterprise-grade security and reliability
- Extensible architecture for custom requirements
By following this guide, you can deploy a production-ready Shibboleth IdP instance with custom Java components that integrate seamlessly with your existing infrastructure and meet specific business requirements.