Custom Authentication and SSO

Level: Expert Module: Architecture 27 min read Lesson 42 of 47

Overview

  • What you’ll learn:
    • How iDempiere’s authentication architecture works, from the Login class through session establishment and security context
    • How to integrate LDAP and Active Directory for centralized user management, and how to implement custom authentication providers
    • How to configure Single Sign-On using OAuth2 and SAML, implement two-factor authentication, and harden security for production deployments
  • Prerequisites: Lesson 40 — System Architecture Design, Lesson 26 — Plugin Development Fundamentals, familiarity with authentication protocols
  • Estimated reading time: 25 minutes

Introduction

Enterprise deployments of iDempiere rarely operate with standalone user management. Organizations have existing identity infrastructure — Active Directory forests, LDAP directories, identity providers like Okta or Azure AD — and they expect iDempiere to participate in their established authentication and authorization ecosystem. Users should not need a separate password for the ERP system, administrators should not need to maintain user accounts in multiple places, and security policies (password complexity, account lockout, multi-factor authentication) should be enforced consistently across all enterprise applications.

This lesson takes you through iDempiere’s authentication architecture, shows you how to integrate with external identity systems, and provides practical guidance on implementing Single Sign-On, custom authentication providers, and security hardening for production environments.

Authentication Architecture in iDempiere

Understanding the default authentication flow is essential before you can extend or replace it. The authentication process in iDempiere follows a well-defined sequence.

The Login Class

The core authentication logic resides in the org.compiere.util.Login class. This class is responsible for:

  1. Credential validation: Checking the username and password against the AD_User table. Passwords are stored as hashed values (SHA-512 with salt in recent versions).
  2. Role enumeration: After successful authentication, querying the AD_User_Roles table to determine which roles the user can assume.
  3. Context population: Once the user selects a role, client, organization, and warehouse, the Login class populates the Env context with session parameters (#AD_Client_ID, #AD_Org_ID, #AD_Role_ID, #M_Warehouse_ID, #AD_User_ID, and many others).
  4. Session creation: Creating an AD_Session record that tracks the login timestamp, client IP address, and session state for audit trail and concurrent session management.

The Web Login Flow

In the ZK web client, the login flow passes through several components:

  1. WLogin.java: The ZK window component that renders the login form and handles user input.
  2. LoginPanel.java: Contains the two-stage login UI — first for username/password, then for role/client/org/warehouse selection.
  3. SessionManager.java: Manages HTTP session binding, session timeout, and session cleanup.
  4. The authentication call chain: LoginPanel calls Login.authenticate(), which returns the list of available roles. After role selection, Login.validateLogin() establishes the full security context.

Key Database Tables

  • AD_User: User accounts, including Password (hashed), Salt, IsLocked, DatePasswordChanged, FailedLoginCount.
  • AD_User_Roles: Many-to-many relationship between users and roles.
  • AD_Role: Role definitions including access permissions.
  • AD_Session: Active and historical session records for auditing.

LDAP Integration

LDAP (Lightweight Directory Access Protocol) integration allows iDempiere to authenticate users against an external directory service, most commonly Microsoft Active Directory. This eliminates the need to maintain separate passwords in iDempiere.

iDempiere’s Built-in LDAP Support

iDempiere includes built-in LDAP configuration at the System level. The configuration is found in the System Configurator or directly in the AD_System record:

  • LDAP Host: The hostname or IP address of the LDAP server (e.g., ldap://ad.company.com or ldaps://ad.company.com:636 for SSL).
  • LDAP Domain: The Active Directory domain (e.g., company.com).

When LDAP is configured, the login process changes: instead of comparing the entered password against the hash stored in AD_User, iDempiere attempts an LDAP bind operation using the provided credentials. If the bind succeeds, the user is authenticated.

Configuration Steps

  1. Set LDAP parameters: In the System window, configure the LDAP Host and Domain fields. For Active Directory, the host is typically ldaps://your-dc.domain.com:636.
  2. Create iDempiere users: Each LDAP user who needs access to iDempiere must still have an AD_User record. The LDAPUser field should contain their LDAP identifier (typically the sAMAccountName or userPrincipalName). The password field in iDempiere can be left empty since authentication will be handled by LDAP.
  3. Assign roles: Role assignment remains in iDempiere. LDAP handles authentication (proving who you are), while iDempiere handles authorization (what you can do).
  4. Test connectivity: Ensure the iDempiere server can reach the LDAP server on the required port (389 for LDAP, 636 for LDAPS). Firewall rules and DNS resolution must be in place.

Practical Example: Active Directory Integration

# LDAP connection test from iDempiere server using ldapsearch
ldapsearch -H ldaps://ad.company.com:636 \
  -D "CN=svc_idempiere,OU=ServiceAccounts,DC=company,DC=com" \
  -W \
  -b "OU=Users,DC=company,DC=com" \
  "(sAMAccountName=jsmith)" \
  cn mail memberOf

This verifies that the iDempiere service account can query the directory and retrieve user attributes. In production, always use LDAPS (LDAP over SSL/TLS) to encrypt credentials in transit.

LDAP Failover

For high availability, configure multiple LDAP servers. If your directory environment has multiple domain controllers, you can specify a space-separated list of hosts or use DNS SRV records. Implement a custom authentication provider (described below) if you need more sophisticated failover logic than the built-in support provides.

Custom Authentication Provider Implementation

When the built-in LDAP support is insufficient — for example, when you need to authenticate against a custom identity store, implement multi-provider authentication (try LDAP first, fall back to database), or add pre/post-authentication logic — you can implement a custom authentication provider as a plugin.

The Authentication Extension Point

iDempiere defines an extension point for authentication through model event handlers and the login process. A custom authentication plugin typically works by intercepting the login process. The approach involves:

  1. Creating an OSGi plugin bundle.
  2. Implementing a factory or service that replaces or wraps the default authentication logic.
  3. Registering the custom authenticator via OSGi Declarative Services or an event handler.

Implementation Pattern

/**
 * Custom authentication provider that checks LDAP first,
 * then falls back to database authentication.
 */
public class CustomAuthenticator {

    private static final String LDAP_URL = "ldaps://ad.company.com:636";
    private static final String LDAP_BASE_DN = "DC=company,DC=com";

    /**
     * Authenticate user against LDAP directory.
     * Returns true if authentication succeeds.
     */
    public boolean authenticateLDAP(String username, String password) {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, LDAP_URL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL,
            username + "@company.com");
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put(Context.SECURITY_PROTOCOL, "ssl");

        try {
            DirContext ctx = new InitialDirContext(env);
            ctx.close();
            return true;  // LDAP bind succeeded
        } catch (AuthenticationException e) {
            log.info("LDAP auth failed for " + username +
                ": " + e.getMessage());
            return false;
        } catch (NamingException e) {
            log.warning("LDAP connection error: " + e.getMessage());
            return false;  // LDAP unavailable, allow fallback
        }
    }

    /**
     * Multi-provider authentication: try LDAP first,
     * then database.
     */
    public boolean authenticate(String username, String password) {
        // Try LDAP authentication first
        if (authenticateLDAP(username, password)) {
            log.info("User " + username +
                " authenticated via LDAP");
            return true;
        }

        // Fall back to database authentication
        // (useful for service accounts or when LDAP is down)
        return authenticateDatabase(username, password);
    }
}

Registering the Custom Provider

The custom authentication provider is registered as an OSGi service. You can hook into the login process by implementing a model event handler that intercepts the AD_User login validation, or by using iDempiere’s event-based extension mechanism to replace the default authenticator. The component definition in OSGI-INF would declare the service and its dependencies on the core authentication interfaces.

OAuth2 as Authentication Provider

OAuth2 enables users to authenticate with iDempiere using their existing identity from providers like Google, Microsoft Azure AD, Okta, or Keycloak. The standard flow for web applications is the Authorization Code Grant.

OAuth2 Authorization Code Flow

  1. User clicks “Sign in with [Provider]” on the iDempiere login page.
  2. iDempiere redirects the browser to the OAuth2 provider’s authorization endpoint with the client ID, redirect URI, requested scopes, and a state parameter (CSRF protection).
  3. User authenticates at the provider (if not already logged in) and grants consent.
  4. Provider redirects back to iDempiere with an authorization code.
  5. iDempiere’s backend exchanges the authorization code for an access token (and optionally a refresh token and ID token) via a server-to-server request.
  6. iDempiere reads the user’s identity from the ID token or by calling the provider’s userinfo endpoint.
  7. iDempiere looks up the corresponding AD_User record (matching on email or a stored provider subject ID), validates that the user is active and has roles assigned, and establishes the iDempiere session.

Implementation Considerations

  • User mapping: The OAuth2 provider returns a subject identifier (email, unique ID). You need a reliable mapping to AD_User records. Store the provider’s subject ID in a custom column on AD_User (e.g., OAuthSubjectID) for exact matching.
  • First-time provisioning: Decide whether to auto-create AD_User records for authenticated users who do not yet exist in iDempiere (just-in-time provisioning), or require an administrator to pre-create accounts.
  • Role selection: After OAuth2 authentication, the user still needs to select a role, client, organization, and warehouse. You can either present the standard role selection dialog or auto-select defaults based on the user’s profile or group membership in the identity provider.
  • Token management: Store access and refresh tokens securely (encrypted in the database or in an HTTP-only secure cookie). Implement token refresh logic to maintain the session without requiring re-authentication.

OpenID Connect (OIDC)

OpenID Connect is an identity layer built on top of OAuth2. While OAuth2 provides authorization (access to resources), OIDC adds authentication (identity verification) by defining a standard ID token format (JWT) and a userinfo endpoint. Most modern identity providers (Azure AD, Okta, Google, Keycloak) support OIDC natively. When implementing SSO for iDempiere, prefer OIDC over plain OAuth2 because it provides standardized identity claims (subject, email, name) and eliminates the need for an additional userinfo API call.

SAML Integration for Enterprise SSO

SAML (Security Assertion Markup Language) is the dominant SSO protocol in enterprise environments. It enables users to authenticate once with their organization’s Identity Provider (IdP) — such as ADFS, Okta, Ping Identity, or Azure AD — and access multiple Service Providers (SP) including iDempiere without entering credentials again.

SAML Flow

  1. SP-initiated SSO: User navigates to iDempiere. iDempiere (acting as SP) detects no active session and generates a SAML AuthnRequest, which it sends to the IdP via browser redirect.
  2. IdP authentication: The IdP authenticates the user (using whatever method it supports — password, MFA, smart card) and generates a SAML Response containing assertions about the user’s identity and attributes.
  3. Assertion consumption: The IdP posts the SAML Response to iDempiere’s Assertion Consumer Service (ACS) URL. iDempiere validates the response signature, checks the assertion conditions (timestamps, audience), and extracts user attributes.
  4. Session establishment: iDempiere maps the SAML NameID or attributes to an AD_User record and establishes the iDempiere session.

Implementation Approach

Implementing SAML in iDempiere requires a plugin that includes a SAML library (such as OpenSAML or OneLogin’s SAML toolkit) and registers the necessary servlet endpoints:

  • Metadata endpoint: Publishes the SP metadata (entity ID, ACS URL, signing certificate) that the IdP needs for configuration.
  • ACS endpoint: Receives and processes the SAML Response from the IdP.
  • SLO endpoint (optional): Handles Single Logout — when the user logs out of the IdP, all SP sessions are terminated.
// SAML Assertion Consumer Service - processing the IdP response
@WebServlet("/saml/acs")
public class SAMLConsumerServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        String samlResponse = request.getParameter("SAMLResponse");

        // Decode and validate the SAML Response
        Response samlResp = decodeSAMLResponse(samlResponse);
        validateSignature(samlResp);
        validateConditions(samlResp);

        // Extract user identity
        String nameId = samlResp.getAssertions().get(0)
            .getSubject().getNameID().getValue();
        String email = getAttributeValue(samlResp, "email");

        // Map to iDempiere user
        int userId = findUserByEmail(email);
        if (userId <= 0) {
            response.sendError(403, "No iDempiere account for: " + email);
            return;
        }

        // Establish iDempiere session
        establishSession(request, userId);
        response.sendRedirect("/webui/");
    }
}

SAML vs OAuth2: When to Use Which

  • SAML: Preferred in traditional enterprise environments with ADFS, Shibboleth, or legacy IdPs. Better for browser-based SSO in corporate intranets. More mature and widely supported in enterprise software.
  • OAuth2/OIDC: Preferred for modern cloud-first environments. Better for API-based authentication. Simpler to implement. Required when integrating with consumer identity providers (Google, GitHub).
  • Both: Many identity providers (Okta, Azure AD, Keycloak) support both protocols. You may implement both in iDempiere to accommodate different integration scenarios.

Custom LoginDialog Modifications

To support SSO and custom authentication, you may need to modify the iDempiere login interface. This is done through a plugin that extends or replaces the default login components.

Adding SSO Buttons

Create a custom login panel that extends the default LoginPanel and adds SSO initiation buttons:

public class SSOLoginPanel extends LoginPanel {

    @Override
    protected void initComponents() {
        super.initComponents();  // Render default login form

        // Add a visual separator
        Separator sep = new Separator();
        sep.setBar(true);
        loginPanel.appendChild(sep);

        Label orLabel = new Label("Or sign in with:");
        loginPanel.appendChild(orLabel);

        // Add SSO button below the standard login form
        Button ssoButton = new Button("Corporate SSO (SAML)");
        ssoButton.addEventListener(Events.ON_CLICK, event -> {
            String ssoUrl = buildSAMLAuthnRequestUrl();
            Executions.getCurrent().sendRedirect(ssoUrl);
        });
        loginPanel.appendChild(ssoButton);

        // Add OAuth2 button
        Button oauthButton = new Button("Sign in with Microsoft");
        oauthButton.addEventListener(Events.ON_CLICK, event -> {
            String oauthUrl = buildOAuthAuthorizationUrl();
            Executions.getCurrent().sendRedirect(oauthUrl);
        });
        loginPanel.appendChild(oauthButton);
    }
}

Registering the Custom Login Panel

Use iDempiere’s form factory mechanism or a ZK page extension to replace the default login panel with your custom version. This is typically done by registering an OSGi component that provides the custom form, overriding the default login window definition in the Application Dictionary or through a ZUL page customization.

Two-Factor Authentication

Two-factor authentication (2FA) adds a second verification step beyond the password. After entering the username and password (first factor — something you know), the user must provide a second factor — typically a time-based one-time password (TOTP) generated by an authenticator app (something you have).

TOTP Implementation Approach

  1. Secret generation: When a user enables 2FA, generate a random secret key and store it (encrypted) in the AD_User table or a related table. Display the secret as a QR code that the user scans with their authenticator app (Google Authenticator, Microsoft Authenticator, Authy).
  2. Code verification: After successful password authentication, present a TOTP code entry dialog. Use a TOTP library (e.g., GoogleAuth for Java) to validate the entered code against the stored secret.
  3. Backup codes: Generate a set of one-time backup codes that the user can store securely for use if they lose access to their authenticator app.
  4. Recovery: Provide an administrative process to reset 2FA for a user who has lost both their authenticator device and backup codes.
// TOTP verification using GoogleAuth library
GoogleAuthenticator gAuth = new GoogleAuthenticator();

// During 2FA enrollment - generate and store secret
GoogleAuthenticatorKey key = gAuth.createCredentials();
String secret = key.getKey();
// Store 'secret' encrypted in AD_User or a custom table
// Generate QR code URL for the authenticator app
String qrUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(
    "iDempiere", user.getEMail(), key);

// During login - verify the TOTP code
boolean isValid = gAuth.authorize(storedSecret, userEnteredCode);
if (!isValid) {
    throw new AuthenticationException("Invalid 2FA code");
}

Session Management and Security

Proper session management is critical for security in any web application, and especially so for an ERP system that handles sensitive financial and business data.

Session Timeout Configuration

Configure appropriate session timeouts in the web.xml or iDempiere system configuration:

  • Idle timeout: Terminate sessions after a period of inactivity (typically 30-60 minutes for ERP systems). Shorter timeouts are more secure but may frustrate users working on long data entry tasks.
  • Absolute timeout: Terminate sessions after a maximum duration regardless of activity (e.g., 8-12 hours) to limit the window of exposure for stolen session tokens.

Concurrent Session Control

The AD_Session table tracks active sessions. You can implement concurrent session control by:

  • Limiting the number of active sessions per user (prevent credential sharing).
  • Terminating the oldest session when a new one is created (single-session policy).
  • Alerting administrators when a user has sessions from multiple IP addresses simultaneously.

Security Hardening for Production

Beyond authentication, a production iDempiere deployment requires comprehensive security hardening.

Password Policies

  • Complexity requirements: Enforce minimum length (12+ characters), character class requirements (uppercase, lowercase, digits, special characters), and prohibit common passwords.
  • Password expiration: Require password changes at regular intervals (90 days is common, though modern guidance from NIST suggests against forced rotation if passwords are strong).
  • Password history: Prevent reuse of recent passwords (maintain a history of the last 10-12 password hashes).

Account Lockout

iDempiere tracks failed login attempts in the AD_User.FailedLoginCount field. Configure automatic account lockout after a threshold of failed attempts (typically 5-10). Implement a lockout duration (e.g., 30 minutes) after which the account automatically unlocks, or require administrative intervention for unlock.

SSL/TLS Configuration

All iDempiere web traffic must be encrypted with TLS in production. The recommended approach is TLS termination at a reverse proxy:

# Nginx reverse proxy with TLS termination for iDempiere
server {
    listen 443 ssl http2;
    server_name erp.company.com;

    ssl_certificate /etc/letsencrypt/live/erp.company.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/erp.company.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Audit Trail

Enable comprehensive auditing for security-sensitive operations:

  • All login and logout events (successful and failed) — tracked in AD_Session and AD_ChangeLog.
  • Changes to security-sensitive records (users, roles, access rules) — tracked by iDempiere’s change log mechanism.
  • Document completions and voids — tracked in the document’s processing history.
  • Export and report execution — for data exfiltration monitoring.

Network Security

  • Restrict direct access to the iDempiere application port (8080) — only the reverse proxy should reach it.
  • Restrict database access to the application server only — never expose PostgreSQL to the internet.
  • Use a Web Application Firewall (WAF) to protect against common web attacks (SQL injection, XSS, CSRF).
  • Implement IP allowlisting for administrative access if possible.

Security Best Practices Summary

The following checklist summarizes the essential security measures for a production iDempiere deployment:

  1. Enforce TLS for all web traffic and database connections.
  2. Integrate with the organization’s identity provider (LDAP, SAML, or OAuth2/OIDC).
  3. Implement two-factor authentication for privileged users at minimum.
  4. Configure password policies appropriate to your organization’s security requirements.
  5. Enable account lockout after failed login attempts.
  6. Set appropriate session timeouts (idle and absolute).
  7. Enable change logging for security-sensitive tables.
  8. Restrict network access using firewalls and security groups.
  9. Regularly review user accounts and role assignments — remove inactive users promptly.
  10. Monitor authentication logs for anomalies (failed login spikes, unusual access patterns, off-hours logins).

Summary

Enterprise authentication in iDempiere extends well beyond the default username/password mechanism. A production deployment should integrate with the organization’s identity infrastructure through LDAP, OAuth2, or SAML, enforce strong security policies, and maintain comprehensive audit trails. The key takeaways are:

  • iDempiere’s authentication is centered on the Login class and can be extended through plugins.
  • LDAP integration is supported natively for basic authentication delegation; custom providers handle more complex scenarios.
  • OAuth2/OIDC and SAML provide enterprise SSO, reducing password fatigue and centralizing identity management.
  • Two-factor authentication adds a critical layer of protection for ERP systems handling sensitive business data.
  • Security hardening is a holistic effort spanning passwords, session management, encryption, and auditing.

In the next lesson, we will tackle data migration — how to plan and execute the transfer of data from legacy systems to iDempiere with minimal risk and maximum confidence.

繁體中文

概述

  • 您將學到:
    • iDempiere 的驗證架構如何運作,從 Login 類別到工作階段建立與安全上下文
    • 如何整合 LDAP 和 Active Directory 以實現集中式使用者管理,以及如何實作自訂驗證提供者
    • 如何使用 OAuth2 和 SAML 設定單一登入、實作雙因素驗證,以及強化生產環境的安全性
  • 先備知識:第 40 課 — 系統架構設計、第 26 課 — 外掛程式開發基礎、熟悉驗證協定
  • 預估閱讀時間:25 分鐘

簡介

iDempiere 的企業部署很少使用獨立的使用者管理。組織擁有既有的身分識別基礎設施 — Active Directory 樹系、LDAP 目錄、Okta 或 Azure AD 等身分識別提供者 — 並且期望 iDempiere 能融入其既有的驗證與授權生態系統。使用者不應為 ERP 系統需要另一組密碼,管理員不應在多個地方維護使用者帳戶,而安全策略(密碼複雜度、帳戶鎖定、多因素驗證)應在所有企業應用程式中一致地執行。

本課程將帶您了解 iDempiere 的驗證架構,展示如何與外部身分識別系統整合,並提供實作單一登入、自訂驗證提供者及生產環境安全強化的實用指南。

iDempiere 中的驗證架構

在擴展或替換預設驗證流程之前,了解它是至關重要的。iDempiere 中的驗證過程遵循一個明確定義的順序。

Login 類別

核心驗證邏輯位於 org.compiere.util.Login 類別中。此類別負責:

  1. 憑證驗證:根據 AD_User 資料表檢查使用者名稱和密碼。密碼以雜湊值儲存(近期版本使用帶有 salt 的 SHA-512)。
  2. 角色列舉:驗證成功後,查詢 AD_User_Roles 資料表以確定使用者可以承擔哪些角色。
  3. 上下文填充:當使用者選擇角色、用戶端、組織和倉庫後,Login 類別會將工作階段參數填充到 Env 上下文中(#AD_Client_ID#AD_Org_ID#AD_Role_ID#M_Warehouse_ID#AD_User_ID 等眾多參數)。
  4. 工作階段建立:建立 AD_Session 記錄,追蹤登入時間戳記、用戶端 IP 位址和工作階段狀態,用於稽核軌跡和並行工作階段管理。

網頁登入流程

在 ZK 網頁用戶端中,登入流程會經過多個元件:

  1. WLogin.java:ZK 視窗元件,負責呈現登入表單並處理使用者輸入。
  2. LoginPanel.java:包含兩階段登入介面 — 首先輸入使用者名稱/密碼,然後選擇角色/用戶端/組織/倉庫。
  3. SessionManager.java:管理 HTTP 工作階段繫結、工作階段逾時和工作階段清除。
  4. 驗證呼叫鏈:LoginPanel 呼叫 Login.authenticate(),返回可用角色列表。選擇角色後,Login.validateLogin() 建立完整的安全上下文。

關鍵資料庫資料表

  • AD_User:使用者帳戶,包括 Password(雜湊值)、SaltIsLockedDatePasswordChangedFailedLoginCount
  • AD_User_Roles:使用者與角色之間的多對多關係。
  • AD_Role:角色定義,包括存取權限。
  • AD_Session:用於稽核的作用中和歷史工作階段記錄。

LDAP 整合

LDAP(輕量級目錄存取協定)整合允許 iDempiere 根據外部目錄服務驗證使用者,最常見的是 Microsoft Active Directory。這消除了在 iDempiere 中維護單獨密碼的需要。

iDempiere 的內建 LDAP 支援

iDempiere 在系統層級包含內建的 LDAP 設定。此設定可在系統設定工具或直接在 AD_System 記錄中找到:

  • LDAP 主機:LDAP 伺服器的主機名稱或 IP 位址(例如 ldap://ad.company.comldaps://ad.company.com:636 用於 SSL)。
  • LDAP 網域:Active Directory 網域(例如 company.com)。

設定 LDAP 後,登入過程會改變:iDempiere 不再將輸入的密碼與儲存在 AD_User 中的雜湊值進行比較,而是嘗試使用提供的憑證執行 LDAP 繫結操作。如果繫結成功,使用者即通過驗證。

設定步驟

  1. 設定 LDAP 參數:在系統視窗中,設定 LDAP 主機和網域欄位。對於 Active Directory,主機通常是 ldaps://your-dc.domain.com:636
  2. 建立 iDempiere 使用者:每個需要存取 iDempiere 的 LDAP 使用者仍然必須有一筆 AD_User 記錄。LDAPUser 欄位應包含其 LDAP 識別碼(通常是 sAMAccountNameuserPrincipalName)。iDempiere 中的密碼欄位可以留空,因為驗證將由 LDAP 處理。
  3. 指派角色:角色指派仍在 iDempiere 中進行。LDAP 處理驗證(證明您是誰),而 iDempiere 處理授權(您可以做什麼)。
  4. 測試連線:確保 iDempiere 伺服器可以在所需的連接埠上連接到 LDAP 伺服器(LDAP 使用 389、LDAPS 使用 636)。防火牆規則和 DNS 解析必須就緒。

實務範例:Active Directory 整合

# LDAP connection test from iDempiere server using ldapsearch
ldapsearch -H ldaps://ad.company.com:636 \
  -D "CN=svc_idempiere,OU=ServiceAccounts,DC=company,DC=com" \
  -W \
  -b "OU=Users,DC=company,DC=com" \
  "(sAMAccountName=jsmith)" \
  cn mail memberOf

這驗證了 iDempiere 服務帳戶可以查詢目錄並擷取使用者屬性。在生產環境中,請務必使用 LDAPS(LDAP over SSL/TLS)來加密傳輸中的憑證。

LDAP 故障轉移

為了高可用性,請設定多個 LDAP 伺服器。如果您的目錄環境有多個網域控制站,您可以指定以空格分隔的主機列表或使用 DNS SRV 記錄。如果您需要比內建支援更精密的故障轉移邏輯,請實作自訂驗證提供者(如下所述)。

自訂驗證提供者實作

當內建的 LDAP 支援不足時 — 例如,當您需要根據自訂身分識別儲存區進行驗證、實作多提供者驗證(先嘗試 LDAP,回退到資料庫)或新增驗證前/後邏輯時 — 您可以將自訂驗證提供者實作為外掛程式。

驗證擴展點

iDempiere 透過模型事件處理常式和登入過程定義了驗證擴展點。自訂驗證外掛程式通常透過攔截登入過程來運作。方法包括:

  1. 建立 OSGi 外掛程式套件。
  2. 實作取代或包裝預設驗證邏輯的工廠或服務。
  3. 透過 OSGi Declarative Services 或事件處理常式註冊自訂驗證器。

實作模式

/**
 * Custom authentication provider that checks LDAP first,
 * then falls back to database authentication.
 */
public class CustomAuthenticator {

    private static final String LDAP_URL = "ldaps://ad.company.com:636";
    private static final String LDAP_BASE_DN = "DC=company,DC=com";

    /**
     * Authenticate user against LDAP directory.
     * Returns true if authentication succeeds.
     */
    public boolean authenticateLDAP(String username, String password) {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, LDAP_URL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL,
            username + "@company.com");
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put(Context.SECURITY_PROTOCOL, "ssl");

        try {
            DirContext ctx = new InitialDirContext(env);
            ctx.close();
            return true;  // LDAP bind succeeded
        } catch (AuthenticationException e) {
            log.info("LDAP auth failed for " + username +
                ": " + e.getMessage());
            return false;
        } catch (NamingException e) {
            log.warning("LDAP connection error: " + e.getMessage());
            return false;  // LDAP unavailable, allow fallback
        }
    }

    /**
     * Multi-provider authentication: try LDAP first,
     * then database.
     */
    public boolean authenticate(String username, String password) {
        // Try LDAP authentication first
        if (authenticateLDAP(username, password)) {
            log.info("User " + username +
                " authenticated via LDAP");
            return true;
        }

        // Fall back to database authentication
        // (useful for service accounts or when LDAP is down)
        return authenticateDatabase(username, password);
    }
}

註冊自訂提供者

自訂驗證提供者註冊為 OSGi 服務。您可以透過實作攔截 AD_User 登入驗證的模型事件處理常式,或使用 iDempiere 基於事件的擴展機制來取代預設驗證器,以掛接到登入過程中。OSGI-INF 中的元件定義會宣告服務及其對核心驗證介面的相依性。

OAuth2 作為驗證提供者

OAuth2 讓使用者能夠使用來自 Google、Microsoft Azure AD、Okta 或 Keycloak 等提供者的既有身分識別向 iDempiere 進行驗證。網頁應用程式的標準流程是授權碼授予。

OAuth2 授權碼流程

  1. 使用者在 iDempiere 登入頁面上點擊「使用 [提供者] 登入」。
  2. iDempiere 將瀏覽器重新導向至 OAuth2 提供者的授權端點,附帶用戶端 ID、重新導向 URI、要求的範圍和 state 參數(CSRF 防護)。
  3. 使用者在提供者處進行驗證(如果尚未登入)並授予同意。
  4. 提供者使用授權碼重新導向回 iDempiere。
  5. iDempiere 的後端透過伺服器對伺服器的請求,將授權碼交換為存取權杖(以及可選的重新整理權杖和 ID 權杖)。
  6. iDempiere 從 ID 權杖或透過呼叫提供者的 userinfo 端點來讀取使用者的身分識別。
  7. iDempiere 查詢對應的 AD_User 記錄(透過電子郵件或儲存的提供者主體 ID 進行比對),驗證使用者處於啟用狀態且已指派角色,然後建立 iDempiere 工作階段。

實作考量

  • 使用者對應:OAuth2 提供者返回主體識別碼(電子郵件、唯一 ID)。您需要可靠地對應到 AD_User 記錄。將提供者的主體 ID 儲存在 AD_User 的自訂欄位中(例如 OAuthSubjectID)以進行精確比對。
  • 首次佈建:決定是否為尚不存在於 iDempiere 中的已驗證使用者自動建立 AD_User 記錄(即時佈建),或要求管理員預先建立帳戶。
  • 角色選擇:OAuth2 驗證後,使用者仍需要選擇角色、用戶端、組織和倉庫。您可以呈現標準的角色選擇對話方塊,或根據使用者在身分識別提供者中的設定檔或群組成員資格自動選擇預設值。
  • 權杖管理:安全地儲存存取權杖和重新整理權杖(在資料庫中加密或儲存在僅限 HTTP 的安全 Cookie 中)。實作權杖重新整理邏輯,以在不需要重新驗證的情況下維護工作階段。

OpenID Connect (OIDC)

OpenID Connect 是建構在 OAuth2 之上的身分識別層。OAuth2 提供授權(存取資源),而 OIDC 透過定義標準 ID 權杖格式(JWT)和 userinfo 端點來增加驗證(身分識別驗證)。大多數現代身分識別提供者(Azure AD、Okta、Google、Keycloak)原生支援 OIDC。為 iDempiere 實作 SSO 時,建議優先選擇 OIDC 而非純 OAuth2,因為它提供標準化的身分識別宣告(subject、email、name)並消除了額外 userinfo API 呼叫的需要。

企業 SSO 的 SAML 整合

SAML(安全斷言標記語言)是企業環境中主流的 SSO 協定。它讓使用者在組織的身分識別提供者(IdP)— 如 ADFS、Okta、Ping Identity 或 Azure AD — 進行一次驗證後,即可存取包括 iDempiere 在內的多個服務提供者(SP),而無需再次輸入憑證。

SAML 流程

  1. SP 發起的 SSO:使用者導覽至 iDempiere。iDempiere(作為 SP)偵測到沒有作用中的工作階段,產生 SAML AuthnRequest,並透過瀏覽器重新導向將其傳送至 IdP。
  2. IdP 驗證:IdP 驗證使用者(使用其支援的任何方法 — 密碼、MFA、智慧卡),並產生包含使用者身分識別和屬性斷言的 SAML Response。
  3. 斷言消費:IdP 將 SAML Response 發佈至 iDempiere 的斷言消費者服務(ACS)URL。iDempiere 驗證回應簽章、檢查斷言條件(時間戳記、受眾),並擷取使用者屬性。
  4. 工作階段建立:iDempiere 將 SAML NameID 或屬性對應到 AD_User 記錄,並建立 iDempiere 工作階段。

實作方法

在 iDempiere 中實作 SAML 需要一個外掛程式,其中包含 SAML 函式庫(如 OpenSAML 或 OneLogin 的 SAML 工具包)並註冊必要的 servlet 端點:

  • 中繼資料端點:發佈 SP 中繼資料(實體 ID、ACS URL、簽署憑證),IdP 需要這些資料進行設定。
  • ACS 端點:接收並處理來自 IdP 的 SAML Response。
  • SLO 端點(選用):處理單一登出 — 當使用者登出 IdP 時,所有 SP 工作階段都會終止。
// SAML Assertion Consumer Service - processing the IdP response
@WebServlet("/saml/acs")
public class SAMLConsumerServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        String samlResponse = request.getParameter("SAMLResponse");

        // Decode and validate the SAML Response
        Response samlResp = decodeSAMLResponse(samlResponse);
        validateSignature(samlResp);
        validateConditions(samlResp);

        // Extract user identity
        String nameId = samlResp.getAssertions().get(0)
            .getSubject().getNameID().getValue();
        String email = getAttributeValue(samlResp, "email");

        // Map to iDempiere user
        int userId = findUserByEmail(email);
        if (userId <= 0) {
            response.sendError(403, "No iDempiere account for: " + email);
            return;
        }

        // Establish iDempiere session
        establishSession(request, userId);
        response.sendRedirect("/webui/");
    }
}

SAML 與 OAuth2:何時使用哪個

  • SAML:在使用 ADFS、Shibboleth 或傳統 IdP 的傳統企業環境中較為適合。更適合企業內部網路中基於瀏覽器的 SSO。在企業軟體中更成熟且被更廣泛支援。
  • OAuth2/OIDC:在現代雲端優先環境中較為適合。更適合基於 API 的驗證。實作更簡單。在與消費者身分識別提供者(Google、GitHub)整合時為必要。
  • 兩者皆可:許多身分識別提供者(Okta、Azure AD、Keycloak)支援這兩種協定。您可以在 iDempiere 中同時實作兩者,以適應不同的整合情境。

自訂 LoginDialog 修改

為了支援 SSO 和自訂驗證,您可能需要修改 iDempiere 的登入介面。這透過擴展或取代預設登入元件的外掛程式來完成。

新增 SSO 按鈕

建立一個擴展預設 LoginPanel 並新增 SSO 啟動按鈕的自訂登入面板:

public class SSOLoginPanel extends LoginPanel {

    @Override
    protected void initComponents() {
        super.initComponents();  // Render default login form

        // Add a visual separator
        Separator sep = new Separator();
        sep.setBar(true);
        loginPanel.appendChild(sep);

        Label orLabel = new Label("Or sign in with:");
        loginPanel.appendChild(orLabel);

        // Add SSO button below the standard login form
        Button ssoButton = new Button("Corporate SSO (SAML)");
        ssoButton.addEventListener(Events.ON_CLICK, event -> {
            String ssoUrl = buildSAMLAuthnRequestUrl();
            Executions.getCurrent().sendRedirect(ssoUrl);
        });
        loginPanel.appendChild(ssoButton);

        // Add OAuth2 button
        Button oauthButton = new Button("Sign in with Microsoft");
        oauthButton.addEventListener(Events.ON_CLICK, event -> {
            String oauthUrl = buildOAuthAuthorizationUrl();
            Executions.getCurrent().sendRedirect(oauthUrl);
        });
        loginPanel.appendChild(oauthButton);
    }
}

註冊自訂登入面板

使用 iDempiere 的表單工廠機制或 ZK 頁面擴展,將預設登入面板替換為您的自訂版本。這通常透過註冊提供自訂表單的 OSGi 元件、覆寫 Application Dictionary 中的預設登入視窗定義或透過 ZUL 頁面自訂來完成。

雙因素驗證

雙因素驗證(2FA)在密碼之外增加了第二道驗證步驟。在輸入使用者名稱和密碼(第一因素 — 您知道的東西)後,使用者必須提供第二因素 — 通常是由驗證器應用程式產生的基於時間的一次性密碼(TOTP)(您擁有的東西)。

TOTP 實作方法

  1. 密鑰產生:當使用者啟用 2FA 時,產生一個隨機密鑰並將其(加密後)儲存在 AD_User 資料表或相關資料表中。將密鑰顯示為 QR 碼,讓使用者使用驗證器應用程式(Google Authenticator、Microsoft Authenticator、Authy)掃描。
  2. 驗證碼驗證:密碼驗證成功後,顯示 TOTP 驗證碼輸入對話方塊。使用 TOTP 函式庫(例如 Java 的 GoogleAuth)根據儲存的密鑰驗證輸入的驗證碼。
  3. 備用碼:產生一組一次性備用碼,使用者可以安全儲存,以便在失去驗證器應用程式存取權時使用。
  4. 復原:提供管理流程,為失去驗證器裝置和備用碼的使用者重設 2FA。
// TOTP verification using GoogleAuth library
GoogleAuthenticator gAuth = new GoogleAuthenticator();

// During 2FA enrollment - generate and store secret
GoogleAuthenticatorKey key = gAuth.createCredentials();
String secret = key.getKey();
// Store 'secret' encrypted in AD_User or a custom table
// Generate QR code URL for the authenticator app
String qrUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(
    "iDempiere", user.getEMail(), key);

// During login - verify the TOTP code
boolean isValid = gAuth.authorize(storedSecret, userEnteredCode);
if (!isValid) {
    throw new AuthenticationException("Invalid 2FA code");
}

工作階段管理與安全

適當的工作階段管理對任何網頁應用程式的安全至關重要,尤其對於處理敏感財務和商業資料的 ERP 系統更是如此。

工作階段逾時設定

web.xml 或 iDempiere 系統設定中設定適當的工作階段逾時:

  • 閒置逾時:在一段不活動期間後終止工作階段(ERP 系統通常為 30-60 分鐘)。較短的逾時更安全,但可能會讓進行長時間資料輸入工作的使用者感到困擾。
  • 絕對逾時:不論活動狀況,在最大持續時間後終止工作階段(例如 8-12 小時),以限制被竊取工作階段權杖的曝光窗口。

並行工作階段控制

AD_Session 資料表追蹤作用中的工作階段。您可以透過以下方式實作並行工作階段控制:

  • 限制每個使用者的作用中工作階段數量(防止憑證共享)。
  • 建立新工作階段時終止最舊的工作階段(單一工作階段策略)。
  • 當使用者同時從多個 IP 位址有工作階段時通知管理員。

生產環境安全強化

除了驗證之外,生產環境的 iDempiere 部署需要全面的安全強化。

密碼策略

  • 複雜度要求:強制最低長度(12 個以上字元)、字元類別要求(大寫、小寫、數字、特殊字元),並禁止常見密碼。
  • 密碼到期:要求定期更改密碼(90 天很常見,但 NIST 的現代指南建議如果密碼夠強則不強制輪換)。
  • 密碼歷程:防止重複使用最近的密碼(維護最近 10-12 個密碼雜湊值的歷程記錄)。

帳戶鎖定

iDempiere 在 AD_User.FailedLoginCount 欄位中追蹤登入失敗次數。在達到失敗次數閾值(通常 5-10 次)後設定自動帳戶鎖定。實作鎖定持續時間(例如 30 分鐘),之後帳戶自動解鎖,或要求管理員介入解鎖。

SSL/TLS 設定

在生產環境中,所有 iDempiere 網頁流量都必須使用 TLS 加密。建議的方法是在反向代理伺服器進行 TLS 終止:

# Nginx reverse proxy with TLS termination for iDempiere
server {
    listen 443 ssl http2;
    server_name erp.company.com;

    ssl_certificate /etc/letsencrypt/live/erp.company.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/erp.company.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

稽核軌跡

為安全敏感操作啟用全面稽核:

  • 所有登入和登出事件(成功和失敗)— 在 AD_SessionAD_ChangeLog 中追蹤。
  • 安全敏感記錄的變更(使用者、角色、存取規則)— 由 iDempiere 的變更記錄機制追蹤。
  • 文件完成和作廢 — 在文件的處理歷程中追蹤。
  • 匯出和報表執行 — 用於資料外洩監控。

網路安全

  • 限制對 iDempiere 應用程式連接埠(8080)的直接存取 — 只有反向代理伺服器可以連接。
  • 將資料庫存取限制為僅限應用程式伺服器 — 絕不將 PostgreSQL 暴露到網際網路。
  • 使用網頁應用程式防火牆(WAF)來防護常見的網頁攻擊(SQL injection、XSS、CSRF)。
  • 如果可能,為管理存取實作 IP 允許清單。

安全最佳實務摘要

以下清單總結了生產環境 iDempiere 部署的基本安全措施:

  1. 對所有網頁流量和資料庫連線強制使用 TLS。
  2. 與組織的身分識別提供者整合(LDAP、SAML 或 OAuth2/OIDC)。
  3. 至少為特權使用者實作雙因素驗證。
  4. 設定適合組織安全要求的密碼策略。
  5. 在登入失敗嘗試後啟用帳戶鎖定。
  6. 設定適當的工作階段逾時(閒置和絕對)。
  7. 為安全敏感資料表啟用變更記錄。
  8. 使用防火牆和安全群組限制網路存取。
  9. 定期檢閱使用者帳戶和角色指派 — 及時移除非作用中使用者。
  10. 監控驗證記錄中的異常情況(登入失敗尖峰、異常存取模式、非工作時間登入)。

摘要

iDempiere 中的企業驗證遠遠超出預設的使用者名稱/密碼機制。生產環境部署應透過 LDAP、OAuth2 或 SAML 與組織的身分識別基礎設施整合、執行強大的安全策略,並維護全面的稽核軌跡。關鍵要點為:

  • iDempiere 的驗證以 Login 類別為核心,可透過外掛程式進行擴展。
  • LDAP 整合原生支援基本驗證委派;自訂提供者處理更複雜的情境。
  • OAuth2/OIDC 和 SAML 提供企業 SSO,減少密碼疲勞並集中身分識別管理。
  • 雙因素驗證為處理敏感商業資料的 ERP 系統增加了關鍵的防護層。
  • 安全強化是一項涵蓋密碼、工作階段管理、加密和稽核的整體工作。

在下一課中,我們將探討資料遷移 — 如何以最低風險和最高信心規劃並執行從舊系統到 iDempiere 的資料轉移。

日本語

概要

  • 学習内容:
    • iDempiere の認証アーキテクチャが Login クラスからセッション確立およびセキュリティコンテキストまでどのように動作するか
    • 集中的なユーザー管理のために LDAP および Active Directory を統合する方法、およびカスタム認証プロバイダーを実装する方法
    • OAuth2 および SAML を使用してシングルサインオンを構成し、二要素認証を実装し、本番デプロイメントのセキュリティを強化する方法
  • 前提条件:レッスン 40 — システムアーキテクチャ設計、レッスン 26 — プラグイン開発の基礎、認証プロトコルに関する知識
  • 推定読了時間:25 分

はじめに

iDempiere のエンタープライズデプロイメントが、スタンドアロンのユーザー管理で運用されることはほとんどありません。組織には既存のアイデンティティ基盤があります — Active Directory フォレスト、LDAP ディレクトリ、Okta や Azure AD などの ID プロバイダー — そして、iDempiere が確立された認証・認可エコシステムに参加することを期待しています。ユーザーは ERP システム用に別のパスワードを必要とすべきではなく、管理者は複数の場所でユーザーアカウントを管理する必要はなく、セキュリティポリシー(パスワードの複雑さ、アカウントロックアウト、多要素認証)はすべてのエンタープライズアプリケーションで一貫して適用されるべきです。

このレッスンでは、iDempiere の認証アーキテクチャを解説し、外部アイデンティティシステムとの統合方法を示し、シングルサインオン、カスタム認証プロバイダー、および本番環境のセキュリティ強化の実装に関する実践的なガイダンスを提供します。

iDempiere の認証アーキテクチャ

デフォルトの認証フローを拡張または置き換える前に、それを理解することが不可欠です。iDempiere の認証プロセスは、明確に定義されたシーケンスに従います。

Login クラス

コア認証ロジックは org.compiere.util.Login クラスに存在します。このクラスは以下を担当します:

  1. 資格情報の検証:AD_User テーブルに対してユーザー名とパスワードを確認します。パスワードはハッシュ値として保存されます(最近のバージョンでは salt 付き SHA-512)。
  2. ロールの列挙:認証成功後、AD_User_Roles テーブルを照会して、ユーザーが引き受けることができるロールを判定します。
  3. コンテキストの設定:ユーザーがロール、クライアント、組織、倉庫を選択すると、Login クラスはセッションパラメータ(#AD_Client_ID#AD_Org_ID#AD_Role_ID#M_Warehouse_ID#AD_User_ID など多数)で Env コンテキストを設定します。
  4. セッションの作成:ログインタイムスタンプ、クライアント IP アドレス、セッション状態を追跡する AD_Session レコードを作成し、監査証跡と同時セッション管理に使用します。

Web ログインフロー

ZK Web クライアントでは、ログインフローはいくつかのコンポーネントを通過します:

  1. WLogin.java:ログインフォームをレンダリングし、ユーザー入力を処理する ZK ウィンドウコンポーネント。
  2. LoginPanel.java:2 段階のログイン UI を含みます — 最初にユーザー名/パスワード、次にロール/クライアント/組織/倉庫の選択。
  3. SessionManager.java:HTTP セッションバインディング、セッションタイムアウト、セッションクリーンアップを管理します。
  4. 認証呼び出しチェーン:LoginPanelLogin.authenticate() を呼び出し、利用可能なロールのリストを返します。ロール選択後、Login.validateLogin() が完全なセキュリティコンテキストを確立します。

主要なデータベーステーブル

  • AD_User:ユーザーアカウント。Password(ハッシュ値)、SaltIsLockedDatePasswordChangedFailedLoginCount を含みます。
  • AD_User_Roles:ユーザーとロール間の多対多の関係。
  • AD_Role:アクセス権限を含むロール定義。
  • AD_Session:監査用のアクティブおよび過去のセッションレコード。

LDAP 統合

LDAP(Lightweight Directory Access Protocol)統合により、iDempiere は外部ディレクトリサービス(最も一般的には Microsoft Active Directory)に対してユーザーを認証できます。これにより、iDempiere で個別のパスワードを管理する必要がなくなります。

iDempiere の組み込み LDAP サポート

iDempiere にはシステムレベルの組み込み LDAP 構成が含まれています。構成はシステムコンフィグレータまたは AD_System レコードに直接あります:

  • LDAP ホスト:LDAP サーバーのホスト名または IP アドレス(例:ldap://ad.company.com または SSL 用の ldaps://ad.company.com:636)。
  • LDAP ドメイン:Active Directory ドメイン(例:company.com)。

LDAP が構成されると、ログインプロセスが変わります:iDempiere は入力されたパスワードを AD_User に保存されたハッシュと比較する代わりに、提供された資格情報を使用して LDAP バインド操作を試みます。バインドが成功すると、ユーザーは認証されます。

構成手順

  1. LDAP パラメータの設定:システムウィンドウで、LDAP ホストとドメインフィールドを構成します。Active Directory の場合、ホストは通常 ldaps://your-dc.domain.com:636 です。
  2. iDempiere ユーザーの作成:iDempiere にアクセスする必要がある各 LDAP ユーザーには、AD_User レコードが必要です。LDAPUser フィールドには LDAP 識別子(通常は sAMAccountName または userPrincipalName)を含める必要があります。認証は LDAP によって処理されるため、iDempiere のパスワードフィールドは空のままにできます。
  3. ロールの割り当て:ロールの割り当ては iDempiere で行います。LDAP は認証(本人確認)を処理し、iDempiere は認可(何ができるか)を処理します。
  4. 接続テスト:iDempiere サーバーが必要なポート(LDAP は 389、LDAPS は 636)で LDAP サーバーに到達できることを確認します。ファイアウォールルールと DNS 解決が整っている必要があります。

実践例:Active Directory 統合

# LDAP connection test from iDempiere server using ldapsearch
ldapsearch -H ldaps://ad.company.com:636 \
  -D "CN=svc_idempiere,OU=ServiceAccounts,DC=company,DC=com" \
  -W \
  -b "OU=Users,DC=company,DC=com" \
  "(sAMAccountName=jsmith)" \
  cn mail memberOf

これにより、iDempiere サービスアカウントがディレクトリを照会し、ユーザー属性を取得できることが確認されます。本番環境では、転送中の資格情報を暗号化するために、必ず LDAPS(LDAP over SSL/TLS)を使用してください。

LDAP フェイルオーバー

高可用性のために、複数の LDAP サーバーを構成します。ディレクトリ環境に複数のドメインコントローラーがある場合、スペース区切りのホストリストまたは DNS SRV レコードを指定できます。組み込みサポートよりも洗練されたフェイルオーバーロジックが必要な場合は、カスタム認証プロバイダー(以下で説明)を実装してください。

カスタム認証プロバイダーの実装

組み込みの LDAP サポートが不十分な場合 — 例えば、カスタムアイデンティティストアに対して認証する必要がある場合、マルチプロバイダー認証(最初に LDAP を試し、データベースにフォールバック)を実装する場合、または認証前後のロジックを追加する場合 — カスタム認証プロバイダーをプラグインとして実装できます。

認証拡張ポイント

iDempiere はモデルイベントハンドラーとログインプロセスを通じて認証の拡張ポイントを定義しています。カスタム認証プラグインは通常、ログインプロセスをインターセプトすることで動作します。アプローチには以下が含まれます:

  1. OSGi プラグインバンドルの作成。
  2. デフォルトの認証ロジックを置き換えるまたはラップするファクトリーまたはサービスの実装。
  3. OSGi Declarative Services またはイベントハンドラーを介したカスタム認証器の登録。

実装パターン

/**
 * Custom authentication provider that checks LDAP first,
 * then falls back to database authentication.
 */
public class CustomAuthenticator {

    private static final String LDAP_URL = "ldaps://ad.company.com:636";
    private static final String LDAP_BASE_DN = "DC=company,DC=com";

    /**
     * Authenticate user against LDAP directory.
     * Returns true if authentication succeeds.
     */
    public boolean authenticateLDAP(String username, String password) {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, LDAP_URL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL,
            username + "@company.com");
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put(Context.SECURITY_PROTOCOL, "ssl");

        try {
            DirContext ctx = new InitialDirContext(env);
            ctx.close();
            return true;  // LDAP bind succeeded
        } catch (AuthenticationException e) {
            log.info("LDAP auth failed for " + username +
                ": " + e.getMessage());
            return false;
        } catch (NamingException e) {
            log.warning("LDAP connection error: " + e.getMessage());
            return false;  // LDAP unavailable, allow fallback
        }
    }

    /**
     * Multi-provider authentication: try LDAP first,
     * then database.
     */
    public boolean authenticate(String username, String password) {
        // Try LDAP authentication first
        if (authenticateLDAP(username, password)) {
            log.info("User " + username +
                " authenticated via LDAP");
            return true;
        }

        // Fall back to database authentication
        // (useful for service accounts or when LDAP is down)
        return authenticateDatabase(username, password);
    }
}

カスタムプロバイダーの登録

カスタム認証プロバイダーは OSGi サービスとして登録されます。AD_User ログイン検証をインターセプトするモデルイベントハンドラーを実装するか、iDempiere のイベントベースの拡張メカニズムを使用してデフォルトの認証器を置き換えることで、ログインプロセスにフックできます。OSGI-INF のコンポーネント定義で、サービスとコア認証インターフェースへの依存関係を宣言します。

認証プロバイダーとしての OAuth2

OAuth2 により、ユーザーは Google、Microsoft Azure AD、Okta、Keycloak などのプロバイダーからの既存のアイデンティティを使用して iDempiere で認証できます。Web アプリケーションの標準フローは認可コードグラントです。

OAuth2 認可コードフロー

  1. ユーザーが iDempiere ログインページで「[プロバイダー] でサインイン」をクリックします。
  2. iDempiere はクライアント ID、リダイレクト URI、要求されたスコープ、state パラメータ(CSRF 保護)とともに、ブラウザを OAuth2 プロバイダーの認可エンドポイントにリダイレクトします。
  3. ユーザーがプロバイダーで認証し(まだログインしていない場合)、同意を付与します。
  4. プロバイダーが認可コードとともに iDempiere にリダイレクトバックします。
  5. iDempiere のバックエンドがサーバー間リクエストを介して、認可コードをアクセストークン(およびオプションでリフレッシュトークンと ID トークン)と交換します。
  6. iDempiere は ID トークンから、またはプロバイダーの userinfo エンドポイントを呼び出すことで、ユーザーのアイデンティティを読み取ります。
  7. iDempiere は対応する AD_User レコードを検索し(メールまたは保存されたプロバイダーサブジェクト ID で照合)、ユーザーがアクティブでロールが割り当てられていることを確認し、iDempiere セッションを確立します。

実装上の考慮事項

  • ユーザーマッピング:OAuth2 プロバイダーはサブジェクト識別子(メール、一意 ID)を返します。AD_User レコードへの信頼性の高いマッピングが必要です。正確な照合のために、プロバイダーのサブジェクト ID を AD_User のカスタム列(例:OAuthSubjectID)に保存します。
  • 初回プロビジョニング:iDempiere にまだ存在しない認証済みユーザーの AD_User レコードを自動作成するか(ジャストインタイムプロビジョニング)、管理者に事前にアカウントを作成することを要求するかを決定します。
  • ロール選択:OAuth2 認証後も、ユーザーはロール、クライアント、組織、倉庫を選択する必要があります。標準のロール選択ダイアログを表示するか、ID プロバイダーでのユーザーのプロファイルまたはグループメンバーシップに基づいてデフォルトを自動選択できます。
  • トークン管理:アクセストークンとリフレッシュトークンを安全に保存します(データベースで暗号化するか、HTTP 専用のセキュア Cookie に保存)。再認証を必要とせずにセッションを維持するために、トークンリフレッシュロジックを実装します。

OpenID Connect (OIDC)

OpenID Connect は OAuth2 の上に構築されたアイデンティティレイヤーです。OAuth2 は認可(リソースへのアクセス)を提供しますが、OIDC は標準 ID トークン形式(JWT)と userinfo エンドポイントを定義することで認証(アイデンティティ検証)を追加します。ほとんどの最新の ID プロバイダー(Azure AD、Okta、Google、Keycloak)は OIDC をネイティブにサポートしています。iDempiere に SSO を実装する場合、標準化されたアイデンティティクレーム(subject、email、name)を提供し、追加の userinfo API 呼び出しの必要性を排除するため、純粋な OAuth2 よりも OIDC を優先してください。

エンタープライズ SSO のための SAML 統合

SAML(Security Assertion Markup Language)はエンタープライズ環境で主流の SSO プロトコルです。ユーザーが組織の ID プロバイダー(IdP)— ADFS、Okta、Ping Identity、Azure AD など — で一度認証すれば、資格情報を再入力することなく、iDempiere を含む複数のサービスプロバイダー(SP)にアクセスできるようにします。

SAML フロー

  1. SP 起点の SSO:ユーザーが iDempiere にアクセスします。iDempiere(SP として動作)はアクティブなセッションがないことを検出し、SAML AuthnRequest を生成して、ブラウザリダイレクトを介して IdP に送信します。
  2. IdP 認証:IdP はユーザーを認証し(サポートする方法 — パスワード、MFA、スマートカード — を使用)、ユーザーのアイデンティティと属性に関するアサーションを含む SAML Response を生成します。
  3. アサーションの消費:IdP は SAML Response を iDempiere の Assertion Consumer Service(ACS)URL にポストします。iDempiere はレスポンスの署名を検証し、アサーション条件(タイムスタンプ、オーディエンス)を確認し、ユーザー属性を抽出します。
  4. セッションの確立:iDempiere は SAML NameID または属性を AD_User レコードにマッピングし、iDempiere セッションを確立します。

実装アプローチ

iDempiere に SAML を実装するには、SAML ライブラリ(OpenSAML や OneLogin の SAML ツールキットなど)を含み、必要なサーブレットエンドポイントを登録するプラグインが必要です:

  • メタデータエンドポイント:IdP が構成に必要とする SP メタデータ(エンティティ ID、ACS URL、署名証明書)を公開します。
  • ACS エンドポイント:IdP からの SAML Response を受信して処理します。
  • SLO エンドポイント(オプション):シングルログアウトを処理します — ユーザーが IdP からログアウトすると、すべての SP セッションが終了します。
// SAML Assertion Consumer Service - processing the IdP response
@WebServlet("/saml/acs")
public class SAMLConsumerServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        String samlResponse = request.getParameter("SAMLResponse");

        // Decode and validate the SAML Response
        Response samlResp = decodeSAMLResponse(samlResponse);
        validateSignature(samlResp);
        validateConditions(samlResp);

        // Extract user identity
        String nameId = samlResp.getAssertions().get(0)
            .getSubject().getNameID().getValue();
        String email = getAttributeValue(samlResp, "email");

        // Map to iDempiere user
        int userId = findUserByEmail(email);
        if (userId <= 0) {
            response.sendError(403, "No iDempiere account for: " + email);
            return;
        }

        // Establish iDempiere session
        establishSession(request, userId);
        response.sendRedirect("/webui/");
    }
}

SAML 対 OAuth2:どちらを使用するか

  • SAML:ADFS、Shibboleth、またはレガシー IdP を使用する従来のエンタープライズ環境で適しています。企業イントラネットでのブラウザベースの SSO に適しています。エンタープライズソフトウェアでより成熟しており、広くサポートされています。
  • OAuth2/OIDC:最新のクラウドファースト環境で適しています。API ベースの認証に適しています。実装がよりシンプルです。コンシューマー ID プロバイダー(Google、GitHub)との統合に必要です。
  • 両方:多くの ID プロバイダー(Okta、Azure AD、Keycloak)は両方のプロトコルをサポートしています。異なる統合シナリオに対応するために、iDempiere に両方を実装できます。

カスタム LoginDialog の変更

SSO とカスタム認証をサポートするために、iDempiere のログインインターフェースを変更する必要がある場合があります。これは、デフォルトのログインコンポーネントを拡張または置き換えるプラグインを通じて行われます。

SSO ボタンの追加

デフォルトの LoginPanel を拡張し、SSO 開始ボタンを追加するカスタムログインパネルを作成します:

public class SSOLoginPanel extends LoginPanel {

    @Override
    protected void initComponents() {
        super.initComponents();  // Render default login form

        // Add a visual separator
        Separator sep = new Separator();
        sep.setBar(true);
        loginPanel.appendChild(sep);

        Label orLabel = new Label("Or sign in with:");
        loginPanel.appendChild(orLabel);

        // Add SSO button below the standard login form
        Button ssoButton = new Button("Corporate SSO (SAML)");
        ssoButton.addEventListener(Events.ON_CLICK, event -> {
            String ssoUrl = buildSAMLAuthnRequestUrl();
            Executions.getCurrent().sendRedirect(ssoUrl);
        });
        loginPanel.appendChild(ssoButton);

        // Add OAuth2 button
        Button oauthButton = new Button("Sign in with Microsoft");
        oauthButton.addEventListener(Events.ON_CLICK, event -> {
            String oauthUrl = buildOAuthAuthorizationUrl();
            Executions.getCurrent().sendRedirect(oauthUrl);
        });
        loginPanel.appendChild(oauthButton);
    }
}

カスタムログインパネルの登録

iDempiere のフォームファクトリーメカニズムまたは ZK ページ拡張を使用して、デフォルトのログインパネルをカスタムバージョンに置き換えます。これは通常、カスタムフォームを提供する OSGi コンポーネントを登録し、Application Dictionary のデフォルトログインウィンドウ定義をオーバーライドするか、ZUL ページのカスタマイズを通じて行われます。

二要素認証

二要素認証(2FA)は、パスワード以外に 2 つ目の検証ステップを追加します。ユーザー名とパスワードを入力した後(第 1 要素 — 知っているもの)、ユーザーは第 2 要素を提供する必要があります — 通常は認証アプリによって生成される時間ベースのワンタイムパスワード(TOTP)(持っているもの)。

TOTP 実装アプローチ

  1. シークレットの生成:ユーザーが 2FA を有効にすると、ランダムなシークレットキーを生成し、AD_User テーブルまたは関連テーブルに(暗号化して)保存します。シークレットを QR コードとして表示し、ユーザーが認証アプリ(Google Authenticator、Microsoft Authenticator、Authy)でスキャンします。
  2. コードの検証:パスワード認証成功後、TOTP コード入力ダイアログを表示します。TOTP ライブラリ(例:Java 用 GoogleAuth)を使用して、保存されたシークレットに対して入力されたコードを検証します。
  3. バックアップコード:ユーザーが認証アプリへのアクセスを失った場合に使用できる、ワンタイムバックアップコードのセットを生成します。
  4. 復旧:認証デバイスとバックアップコードの両方を紛失したユーザーの 2FA をリセットする管理プロセスを提供します。
// TOTP verification using GoogleAuth library
GoogleAuthenticator gAuth = new GoogleAuthenticator();

// During 2FA enrollment - generate and store secret
GoogleAuthenticatorKey key = gAuth.createCredentials();
String secret = key.getKey();
// Store 'secret' encrypted in AD_User or a custom table
// Generate QR code URL for the authenticator app
String qrUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(
    "iDempiere", user.getEMail(), key);

// During login - verify the TOTP code
boolean isValid = gAuth.authorize(storedSecret, userEnteredCode);
if (!isValid) {
    throw new AuthenticationException("Invalid 2FA code");
}

セッション管理とセキュリティ

適切なセッション管理は、あらゆる Web アプリケーションのセキュリティにとって重要であり、機密性の高い財務データやビジネスデータを扱う ERP システムでは特に重要です。

セッションタイムアウトの構成

web.xml または iDempiere システム構成で適切なセッションタイムアウトを構成します:

  • アイドルタイムアウト:一定期間の非アクティブ後にセッションを終了します(ERP システムでは通常 30〜60 分)。タイムアウトが短いほどセキュリティは高まりますが、長時間のデータ入力作業を行うユーザーにとっては不便になる可能性があります。
  • 絶対タイムアウト:アクティビティに関係なく、最大期間後にセッションを終了します(例:8〜12 時間)。盗まれたセッショントークンの露出ウィンドウを制限します。

同時セッション制御

AD_Session テーブルはアクティブなセッションを追跡します。以下の方法で同時セッション制御を実装できます:

  • ユーザーごとのアクティブセッション数を制限する(資格情報の共有を防止)。
  • 新しいセッションが作成されたときに最も古いセッションを終了する(シングルセッションポリシー)。
  • ユーザーが同時に複数の IP アドレスからセッションを持っている場合に管理者に警告する。

本番環境のセキュリティ強化

認証以外にも、本番環境の iDempiere デプロイメントには包括的なセキュリティ強化が必要です。

パスワードポリシー

  • 複雑さの要件:最小長(12 文字以上)、文字クラスの要件(大文字、小文字、数字、特殊文字)を強制し、一般的なパスワードを禁止します。
  • パスワードの有効期限:定期的なパスワード変更を要求します(90 日が一般的ですが、NIST の最新ガイダンスではパスワードが強力であれば強制ローテーションに反対しています)。
  • パスワード履歴:最近のパスワードの再使用を防止します(直近 10〜12 個のパスワードハッシュの履歴を維持)。

アカウントロックアウト

iDempiere は AD_User.FailedLoginCount フィールドでログイン失敗回数を追跡します。失敗回数のしきい値(通常 5〜10 回)に達した後、自動アカウントロックアウトを構成します。ロックアウト期間(例:30 分)を実装してアカウントを自動的にロック解除するか、管理者の介入によるロック解除を要求します。

SSL/TLS 構成

本番環境では、すべての iDempiere Web トラフィックを TLS で暗号化する必要があります。推奨されるアプローチは、リバースプロキシでの TLS ターミネーションです:

# Nginx reverse proxy with TLS termination for iDempiere
server {
    listen 443 ssl http2;
    server_name erp.company.com;

    ssl_certificate /etc/letsencrypt/live/erp.company.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/erp.company.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

監査証跡

セキュリティ上重要な操作に対して包括的な監査を有効にします:

  • すべてのログインおよびログアウトイベント(成功と失敗)— AD_SessionAD_ChangeLog で追跡。
  • セキュリティ上重要なレコードの変更(ユーザー、ロール、アクセスルール)— iDempiere の変更ログメカニズムで追跡。
  • ドキュメントの完了と無効化 — ドキュメントの処理履歴で追跡。
  • エクスポートとレポートの実行 — データ流出の監視用。

ネットワークセキュリティ

  • iDempiere アプリケーションポート(8080)への直接アクセスを制限します — リバースプロキシのみがアクセスできるようにします。
  • データベースアクセスをアプリケーションサーバーのみに制限します — PostgreSQL をインターネットに公開しないでください。
  • 一般的な Web 攻撃(SQL injection、XSS、CSRF)から保護するために Web アプリケーションファイアウォール(WAF)を使用します。
  • 可能であれば、管理アクセスに IP 許可リストを実装します。

セキュリティベストプラクティスのまとめ

以下のチェックリストは、本番環境の iDempiere デプロイメントに必要不可欠なセキュリティ対策をまとめたものです:

  1. すべての Web トラフィックとデータベース接続に TLS を強制する。
  2. 組織の ID プロバイダーと統合する(LDAP、SAML、または OAuth2/OIDC)。
  3. 少なくとも特権ユーザーに対して二要素認証を実装する。
  4. 組織のセキュリティ要件に適したパスワードポリシーを構成する。
  5. ログイン失敗後のアカウントロックアウトを有効にする。
  6. 適切なセッションタイムアウト(アイドルおよび絶対)を設定する。
  7. セキュリティ上重要なテーブルの変更ログを有効にする。
  8. ファイアウォールとセキュリティグループを使用してネットワークアクセスを制限する。
  9. ユーザーアカウントとロールの割り当てを定期的にレビューする — 非アクティブなユーザーを速やかに削除する。
  10. 認証ログの異常を監視する(ログイン失敗の急増、異常なアクセスパターン、営業時間外のログイン)。

まとめ

iDempiere におけるエンタープライズ認証は、デフォルトのユーザー名/パスワードメカニズムをはるかに超えています。本番環境のデプロイメントでは、LDAP、OAuth2、または SAML を通じて組織のアイデンティティ基盤と統合し、強力なセキュリティポリシーを適用し、包括的な監査証跡を維持する必要があります。主な要点は以下の通りです:

  • iDempiere の認証は Login クラスを中心としており、プラグインを通じて拡張できます。
  • LDAP 統合は基本的な認証委任をネイティブにサポートしています。カスタムプロバイダーはより複雑なシナリオに対応します。
  • OAuth2/OIDC と SAML はエンタープライズ SSO を提供し、パスワード疲れを軽減し、アイデンティティ管理を集約します。
  • 二要素認証は、機密性の高いビジネスデータを扱う ERP システムに重要な保護レイヤーを追加します。
  • セキュリティ強化は、パスワード、セッション管理、暗号化、監査にまたがる包括的な取り組みです。

次のレッスンでは、データ移行について取り上げます — レガシーシステムから iDempiere へのデータ転送を、最小限のリスクと最大限の信頼性で計画・実行する方法です。

You Missed