pac4j
Authentication Library
pac4j
Overview
pac4j is a comprehensive security engine for Java that provides authentication, authorization, and multi-framework support.
Details
pac4j is a powerful and flexible security framework for Java applications. Provided under the Apache 2 license, it supports diverse authentication mechanisms including OAuth, CAS, SAML, OpenID Connect, LDAP, JWT, MongoDB, and CouchDB. It also provides authorization features such as roles, anonymous/remember-me/fully authenticated users, profile type and attribute checks. It can integrate with almost all Java frameworks including Spring Boot, Play Framework, Vert.x, Spark Java, JAX-RS, and Dropwizard. It provides two client types: DirectClient and IndirectClient, supporting authentication for both web services and UI applications. Advanced security features like CSRF protection, security headers, and IP address restrictions are built-in. The configuration-based approach allows easy construction of clients and authenticators from property files.
Pros and Cons
Pros
- Diverse Authentication Support: Wide support for OAuth, SAML, OpenID Connect, LDAP, JWT, etc.
- Framework Agnostic: Compatible with almost all Java frameworks like Spring Boot, Play, Vert.x
- Unified API: Provides all authentication mechanisms through a unified API
- Flexible Authorization: Fine-grained authorization control through roles, attributes, profile types
- Built-in Security: CSRF, security headers, session protection included
- External Configuration: Configuration management through property files
- Active Community: Continuous development and support
Cons
- Learning Curve: Requires understanding of concepts due to extensive functionality
- Configuration Complexity: Advanced features require detailed configuration
- Java Limited: Cannot be used outside Java ecosystem
- Documentation: Some integrations lack detailed documentation
- Debug Complexity: Multi-layered authentication flows can be difficult to debug
- Dependencies: Additional dependencies required for each provider
Key Links
Code Examples
Basic Spring Boot Integration
// application.properties
pac4j.callbackUrl=http://localhost:8080/callback
pac4j.properties.oidc.id=your-client-id
pac4j.properties.oidc.secret=your-client-secret
pac4j.properties.oidc.issuer=https://accounts.google.com
// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public Config config() {
// Google OpenID Connect configuration
OidcConfiguration oidcConfig = new OidcConfiguration();
oidcConfig.setClientId("your-client-id");
oidcConfig.setSecret("your-client-secret");
oidcConfig.setIssuer("https://accounts.google.com");
OidcClient oidcClient = new OidcClient(oidcConfig);
oidcClient.setName("Google");
// Clients configuration
Clients clients = new Clients("http://localhost:8080/callback", oidcClient);
// Build configuration
return new Config(clients);
}
@Bean
public SecurityInterceptor securityInterceptor(Config config) {
return new SecurityInterceptor(config, "Google");
}
}
Multiple Authentication Providers
@Configuration
public class AuthenticationConfig {
@Bean
public Config multiProviderConfig() {
// Google OpenID Connect
OidcConfiguration googleConfig = new OidcConfiguration();
googleConfig.setClientId("google-client-id");
googleConfig.setSecret("google-client-secret");
googleConfig.setIssuer("https://accounts.google.com");
OidcClient googleClient = new OidcClient(googleConfig);
googleClient.setName("Google");
// Facebook OAuth
FacebookConfiguration facebookConfig = new FacebookConfiguration();
facebookConfig.setKey("facebook-app-id");
facebookConfig.setSecret("facebook-app-secret");
facebookConfig.setScope("email,user_profile");
FacebookClient facebookClient = new FacebookClient(facebookConfig);
// SAML configuration
SAML2Configuration samlConfig = new SAML2Configuration();
samlConfig.setKeystorePath("samlKeystore.jks");
samlConfig.setKeystorePassword("password");
samlConfig.setPrivateKeyPassword("password");
samlConfig.setIdentityProviderMetadataPath("idp-metadata.xml");
SAML2Client samlClient = new SAML2Client(samlConfig);
// JWT configuration
SecretSignatureConfiguration jwtConfig =
new SecretSignatureConfiguration("myJwtSecret");
JwtAuthenticator jwtAuthenticator =
new JwtAuthenticator(jwtConfig);
HeaderClient jwtClient = new HeaderClient("Authorization",
"Bearer ", jwtAuthenticator);
Clients clients = new Clients("http://localhost:8080/callback",
googleClient, facebookClient, samlClient, jwtClient);
return new Config(clients);
}
}
Security Control and Authorizers
@RestController
@RequestMapping("/api")
public class ApiController {
// Authentication required
@GetMapping("/secure")
@RequiresAuthentication
public String secureEndpoint(HttpServletRequest request) {
ProfileManager manager = new ProfileManager(
new JEEContext(request, null));
Optional<UserProfile> profile = manager.get(true);
return "Hello " + profile.get().getDisplayName();
}
// Specific role required
@GetMapping("/admin")
@RequiresRoles("ADMIN")
public String adminEndpoint() {
return "Admin area";
}
// Custom authorization
@GetMapping("/custom")
@RequiresAuthorization("customAuthorizer")
public String customEndpoint() {
return "Custom authorized content";
}
}
// Custom authorizer
@Component("customAuthorizer")
public class CustomAuthorizer implements Authorizer<UserProfile> {
@Override
public boolean isAuthorized(WebContext context,
List<UserProfile> profiles) {
if (profiles.isEmpty()) {
return false;
}
UserProfile profile = profiles.get(0);
return profile.containsAttribute("department") &&
"IT".equals(profile.getAttribute("department"));
}
}
LDAP Authentication Configuration
@Configuration
public class LdapAuthConfig {
@Bean
public Config ldapConfig() {
// LDAP configuration
LdapProfile ldapProfile = new LdapProfile();
ldapProfile.setId("cn=admin,dc=example,dc=com");
ldapProfile.setPassword("admin_password");
LdaptiveAuthenticator ldapAuthenticator =
new LdaptiveAuthenticator("ldap://localhost:389",
"dc=example,dc=com",
"cn=%s,ou=users,dc=example,dc=com");
UsernamePasswordCredentials credentials =
new UsernamePasswordCredentials("admin", "password");
IndirectBasicAuthClient ldapClient =
new IndirectBasicAuthClient(ldapAuthenticator);
Clients clients = new Clients("http://localhost:8080/callback",
ldapClient);
return new Config(clients);
}
}
Profile Attribute Management
@Service
public class ProfileService {
public void handleUserProfile(HttpServletRequest request) {
JEEContext context = new JEEContext(request, null);
ProfileManager manager = new ProfileManager(context);
Optional<UserProfile> profile = manager.get(true);
if (profile.isPresent()) {
UserProfile userProfile = profile.get();
// Get basic information
String userId = userProfile.getId();
String displayName = userProfile.getDisplayName();
String email = (String) userProfile.getAttribute("email");
// Check roles
Set<String> roles = userProfile.getRoles();
boolean isAdmin = roles.contains("ADMIN");
// Check permissions
Set<String> permissions = userProfile.getPermissions();
boolean canRead = permissions.contains("READ");
// Custom attributes
String department = (String) userProfile.getAttribute("department");
// Business logic processing
processUserData(userId, displayName, email, department, isAdmin);
}
}
private void processUserData(String userId, String displayName,
String email, String department, boolean isAdmin) {
// User data processing logic
}
}
Session Management and Logout
@Controller
public class AuthController {
@RequestMapping("/login")
public String login(HttpServletRequest request,
@RequestParam(required = false) String client) {
JEEContext context = new JEEContext(request, null);
SecurityLogic<Object, JEEContext> securityLogic =
DefaultSecurityLogic.INSTANCE;
if (client != null) {
// Login with specific client
return "redirect:/callback?client_name=" + client;
}
return "login";
}
@RequestMapping("/logout")
public String logout(HttpServletRequest request,
HttpServletResponse response) {
JEEContext context = new JEEContext(request, response);
ProfileManager manager = new ProfileManager(context);
// Local logout
manager.logout();
// Invalidate session
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return "redirect:/";
}
@RequestMapping("/profile")
public String profile(HttpServletRequest request, Model model) {
JEEContext context = new JEEContext(request, null);
ProfileManager manager = new ProfileManager(context);
Optional<UserProfile> profile = manager.get(true);
if (profile.isPresent()) {
model.addAttribute("profile", profile.get());
model.addAttribute("attributes", profile.get().getAttributes());
}
return "profile";
}
}