pac4j
認証ライブラリ
pac4j
概要
pac4jは、Java向けの包括的なセキュリティエンジンで、認証・認可・マルチフレームワーク対応を提供します。
詳細
pac4jは、Javaアプリケーション向けの強力で柔軟なセキュリティフレームワークです。Apache 2ライセンスの下で提供され、OAuth、CAS、SAML、OpenID Connect、LDAP、JWT、MongoDB、CouchDBなど多様な認証メカニズムをサポートしています。ロール、匿名/記憶/完全認証ユーザー、プロファイルタイプと属性チェックなどの認可機能も提供します。Spring Boot、Play Framework、Vert.x、Spark Java、JAX-RS、Dropwizardなど、ほぼ全てのJavaフレームワークと統合可能です。DirectClientとIndirectClientの2つのクライアントタイプを提供し、WebサービスとUIアプリケーション両方の認証に対応します。CSRF保護、セキュリティヘッダー、IPアドレス制限などの高度なセキュリティ機能も内蔵しています。設定ベースのアプローチにより、プロパティファイルから簡単にクライアントとオーセンティケーターを構築できます。
メリット・デメリット
メリット
- 多様な認証対応: OAuth、SAML、OpenID Connect、LDAP、JWT等幅広くサポート
- フレームワーク横断: Spring Boot、Play、Vert.x等ほぼ全Javaフレームワーク対応
- 統合API: 全認証メカニズムを統一されたAPIで提供
- 柔軟な認可: ロール、属性、プロファイルタイプによる細かい認可制御
- セキュリティ機能: CSRF、セキュリティヘッダー、セッション保護内蔵
- 設定の外部化: プロパティファイルによる設定管理
- 活発なコミュニティ: 継続的な開発とサポート
デメリット
- 学習コスト: 多機能ゆえの概念習得が必要
- 設定複雑さ: 高度な機能には詳細な設定が必要
- Java限定: Java生態系外では使用不可
- ドキュメント: 一部の統合で詳細ドキュメントが不足
- デバッグ複雑さ: 多層的な認証フローのデバッグが困難
- 依存関係: 各プロバイダーごとに追加依存関係が必要
主要リンク
書き方の例
基本的なSpring Boot統合
// 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設定
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 clients = new Clients("http://localhost:8080/callback", oidcClient);
// 設定組み立て
return new Config(clients);
}
@Bean
public SecurityInterceptor securityInterceptor(Config config) {
return new SecurityInterceptor(config, "Google");
}
}
複数認証プロバイダーの設定
@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設定
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設定
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);
}
}
セキュリティ制御とオーソライザー
@RestController
@RequestMapping("/api")
public class ApiController {
// 認証必須
@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();
}
// 特定ロール必須
@GetMapping("/admin")
@RequiresRoles("ADMIN")
public String adminEndpoint() {
return "Admin area";
}
// カスタム認可
@GetMapping("/custom")
@RequiresAuthorization("customAuthorizer")
public String customEndpoint() {
return "Custom authorized content";
}
}
// カスタムオーソライザー
@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認証設定
@Configuration
public class LdapAuthConfig {
@Bean
public Config ldapConfig() {
// LDAP設定
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);
}
}
プロファイル属性管理
@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();
// 基本情報取得
String userId = userProfile.getId();
String displayName = userProfile.getDisplayName();
String email = (String) userProfile.getAttribute("email");
// ロール確認
Set<String> roles = userProfile.getRoles();
boolean isAdmin = roles.contains("ADMIN");
// 権限確認
Set<String> permissions = userProfile.getPermissions();
boolean canRead = permissions.contains("READ");
// カスタム属性
String department = (String) userProfile.getAttribute("department");
// ビジネスロジック処理
processUserData(userId, displayName, email, department, isAdmin);
}
}
private void processUserData(String userId, String displayName,
String email, String department, boolean isAdmin) {
// ユーザーデータ処理ロジック
}
}
セッション管理とログアウト
@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) {
// 特定クライアントでログイン
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);
// ローカルログアウト
manager.logout();
// セッション無効化
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";
}
}