Fork me on GitHub

1) Let’s jump right in with a Spring Boot demo secured by an OpenID Connect server

a) The dependencies: pom.xml

<!-- Spring Boot web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- pac4j implementation for Spring MVC so for Spring Boot as well -->
<dependency>
    <groupId>org.pac4j</groupId>
    <artifactId>spring-webmvc-pac4j</artifactId>
    <version>8.0.2</version>
</dependency>
<!-- pac4j support for OpenID Connect -->
<dependency>
    <groupId>org.pac4j</groupId>
    <artifactId>pac4j-oidc</artifactId>
    <version>6.4.0</version>
</dependency>

b) The SpringBootDemo

@SpringBootApplication
public class SpringBootDemo {
    public static void main(final String[] args) {
        SpringApplication.run(SpringBootDemo.class, args);
    }
}

Just the usual, nothing to say. Run this class.

c) The SecurityConfig for OpenID Connect

@Configuration
public class SecurityConfig extends Pac4jSecurityConfig {

    @Value("${app.base-url:http://localhost:8080}")
    private String baseUri;

    @Bean
    public Config config() {
        // configuration of the authentication via the OpenID Connect protocol
        final var config = new OidcConfiguration()
            .setDiscoveryURI("https://casserverpac4j.herokuapp.com/oidc/.well-known/openid-configuration")
            .setClientId("myclient")
            .setSecret("mysecret")
            .setAllowUnsignedIdTokens(true);
        return new Config(baseUri + "/callback", new OidcClient(config));
    }

    @Override
    public void addInterceptors(final InterceptorRegistry registry) {
        // the /protected/** URLs require the OIDC authentication
        addSecurity(registry, "OidcClient").addPathPatterns("/protected/**");
    }
}

Notice the extends Pac4jSecurityConfig.

Two definitions are required:

Automatically, the /callback and /logout endpoints are created.

d) The controller

@Autowired
private ProfileManager profileManager;

@RequestMapping("/")
@ResponseBody
public String index() {
    return "<h1>Public area</h1><p><a href='/protected/index'>Protected area</a></p>"
            + "<p><a href='/logout'>Logout</a></p>" + profileManager.getProfiles();
}

@RequestMapping("/protected/index")
@ResponseBody
public String secure() {
    return "<h1>Protected area</h1><a href='/'>Home</a><p/>"
            + "<p><a href='/logout'>Logout</a></p>" + profileManager.getProfiles();
}

The injected ProfileManager gives you access to the authenticated user.

2) Let’s switch to a Spring Boot demo secured by a CAS server

Nothing to change, except one dependency:

<!-- pac4j support for CAS -->
<dependency>
    <groupId>org.pac4j</groupId>
    <artifactId>pac4j-cas</artifactId>
    <version>6.4.0</version>
</dependency>

(instead of pac4j-oidc) and the security configuration:

@Value("${cas.login-url:https://casserverpac4j.herokuapp.com/login}")
private String casLoginUrl;

@Bean
public Config config() {
    // configuration of the authentication via the CAS protocol
    return new Config(baseUri + "/callback", new CasClient(new CasConfiguration(casLoginUrl)));
}

@Override
public void addInterceptors(final InterceptorRegistry registry) {
    // the /protected/** URLs require the CAS authentication
    addSecurity(registry, "CasClient").addPathPatterns("/protected/**");
}

A new client (= authn mechanism): CasClient for the CAS protocol (like OidcClient for the OIDC protocol).

The CasConfiguration class is optional on such an easy configuration: return new Config(baseUri + "/callback", new CasClient(casLoginUrl));.

3) Let’s update to a Spring Boot demo secured by a SAML2 IdP

We need a new dependency for the SAML2 protocol support:

<!-- pac4j support for SAML2 -->
<dependency>
    <groupId>org.pac4j</groupId>
    <artifactId>pac4j-saml</artifactId>
    <version>6.4.0</version>
</dependency>

and a new SecurityConfig:

@Bean
public Config config() {
    // configuration of the authentication via the SAML2 protocol
    final var cfg = new SAML2Configuration();
    cfg.getKeystore().setKeystorePath("classpath:samlKeystore.jks");
    cfg.getKeystore().setKeystorePassword("pac4j-demo-passwd");
    cfg.getKeystore().setPrivateKeyPassword("pac4j-demo-passwd");
    cfg.setIdentityProviderMetadataPath("https://casserverpac4j.herokuapp.com/idp/metadata");
    cfg.setServiceProviderEntityId(baseUri + "/callback?client_name=SAML2Client");
    cfg.setServiceProviderMetadataPath("file:metadata/sp-metadata-8080.xml");
    return new Config(baseUri + "/callback", new SAML2Client(cfg));
}

@Override
public void addInterceptors(final InterceptorRegistry registry) {
    // the /protected/** URLs require the SAML2 authentication
    addSecurity(registry, "SAML2Client").addPathPatterns("/protected/**");
}

The SAML2Configuration defines the keystore (in the classpath), its passwords, the SAML2 server metadata, the SP (= pac4j = client) identifier as well as the location of its generated metadata.

A little more complicated but the SAML2 protocol requires that and generally a lot more :-(

Discover more pac4j frameworks and more authentication mechanisms


4) The grand tour