Keycloak concurrency issue while updating roles of a user after authentication with external server

I’m facing an issue with Keycloak while using an external user storage provider. The problem occurs when the same user tries to log in concurrently on multiple devices. Here are the details:

Scenario:

  • A user attempts multiple concurrent logins.
  • Each login request is authenticated, and the user’s roles are updated.
  • This update involves deleting the user’s existing roles and then fetching and assigning new roles from the external TACACS+ user storage.
  • The deletion of roles is causing an OptimisticLockException.
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.user.UserLookupProvider;
...

public class MyUserStorageProvider implements UserStorageProvider, UserLookupProvider,
                    CredentialInputValidator, CredentialInputUpdater
{

    private KeycloakSession session;
    ...

    // constructor
    public MyUserStorageProvider(KeycloakSession session, ...)
    {
        this.session = session;
        ...
    }

    // implementation of org.keycloak.credential.CredentialInputValidator#isValid
    @Override
    public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput)
    {
        ...
        UserAuthenticationAuthorizationModel authModel = authenticateUserWithExternalServer(...);
        
        if (authModel.isAuthenticated())
        {
            // delete all the existing roles of the user
            List<RoleModel> roleMappings = user.getRoleMappingsStream().collect(Collectors.toList());
            for (RoleModel role : roleMappings)
            {
                user.deleteRoleMapping(role); // ---> ISSUE HERE
            }

            // add all the new roles
            if (authModel.getRoles() != null && !authModel.getRoles().isEmpty())
            {
                for (String role : authModel.getRoles())
                {
                    try
                    {
                        RoleModel roleModel = UserFederationUtil.getRoleFromString(realm, role);
                        if (roleModel != null && !user.hasRole(roleModel))
                        {
                            user.grantRole(roleModel);
                        }
                    }
                    catch (Exception e)
                    {
                         ...
                    }
                }
            }
        }
        return authAutorizationModel.isAuthenticated();
    }

   ...
}

Stack trace:

org.keycloak.models.ModelException: javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
        at [email protected]//org.keycloak.connections.jpa.PersistenceExceptionConverter.convert(PersistenceExceptionConverter.java:61 undefined)
        at [email protected]//org.keycloak.connections.jpa.PersistenceExceptionConverter.invoke(PersistenceExceptionConverter.java:51 undefined)
   at [email protected]//com.sun.proxy.$Proxy126.flush(Unknown Source)
        at [email protected]//org.keycloak.storage.jpa.JpaUserFederatedStorageProvider.deleteRoleMapping(JpaUserFederatedStorageProvider.java:546 undefined)
        at [email protected]//org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage.deleteRoleMapping(AbstractUserAdapterFederatedStorage.java:230 undefined)
        at [email protected]//org.keycloak.models.cache.infinispan.UserAdapter.deleteRoleMapping(UserAdapter.java:323 undefined)
   at com.myproject.user.federation.providers.MyUserStorageProvider.isValid(MyUserStorageProvider.java:136 undefined)
...
Caused by: javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
   at [email protected]//org.hibernate.internal.ExceptionConverterImpl.wrapStaleStateException(ExceptionConverterImpl.java:238 undefined)
   at [email protected]//org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java: 93)
   at [email protected]//org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java: 181)
   at [email protected]//org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java: 188)
   at [email protected]//org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1478 undefined)
   at [email protected]//org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1458 undefined)
   at jdk.internal.reflect.GeneratedMethodAccessor428.invoke(Unknown Source)
   at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 undefined)
   at java.base/java.lang.reflect.Method.invoke(Method.java:566 undefined)
        at [email protected]//org.keycloak.connections.jpa.PersistenceExceptionConverter.invoke(PersistenceExceptionConverter.java:49 undefined)
        ... 92 more
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
   at [email protected]//org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:67 undefined)
   at [email protected]//org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:54 undefined)
   at [email protected]//org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46 undefined)
   at [email protected]//org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3498 undefined)
   at [email protected]//org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3755 undefined)
   at [email protected]//org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:99 undefined)
   at [email protected]//org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604 undefined)
   at [email protected]//org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478 undefined)
   at [email protected]//org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356 undefined)
   at [email protected]//org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39 undefined)
   at [email protected]//org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1472 undefined)
        ... 97 more

From this Stack Overflow answer, I understand that an optimistic locking exception occurs when another transaction has committed changes to the entities being updated or deleted. I presumed that other threads calling the same isValid(...) method were modifying the UserModel object concurrently.

To address this, I surrounded the deletion and role assignment logic within a synchronized block. However, this did not resolve the issue. It seems that the session is not immediately flushed, resulting in commits not occurring sequentially, even with synchronization.

As a workaround, I implemented a lock mechanism before modifying the user roles. I release the lock in the close() method of Keycloak Provider (org.keycloak.provider.Provider.close()), which is invoked after the user update is flushed to the database. This ensures that only one thread can update the user’s roles at a time and that the user resource in the DB is updated before the next thread deletes the user roles. However, I’m concerned that this might cause side effects and believe there might be a better solution to update the roles.

Has anyone faced a similar issue or can suggest a more robust solution to handle concurrent logins and role updates in Keycloak with an external user storage? Any help or guidance would be greatly appreciated.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật