Расширенные настройки

Следующие инструкции описывают, как расширить конфигурацию Jmix SAML по умолчанию. Используйте их после выполнения Настройки SAML в Keycloak или аналогичной базовой настройки для другого провайдера.

Сопоставление SAML атрибутов

Прежде чем сопоставлять атрибуты в приложении, убедитесь, что они включены в SAML assertion. См. добавление атрибутов SAML в Keycloak или аналогичные шаги для вашего провайдера.

Если SAML assertion содержит информацию о пользователе, такую как имя, должность, отдел или другие данные профиля, вы можете сделать ее доступной в приложении во время пользовательской сессии.

Для этого:

  1. Создайте класс, расширяющий DefaultJmixSamlUserDetails:

    public class MyUser extends DefaultJmixSamlUserDetails {
    
        private String position; (1)
    
        public String getPosition() {
            return position;
        }
    
        public void setPosition(String position) {
            this.position = position;
        }
    }
    1 Дополнительное поле, соответствующее дополнительному атрибуту SAML.
  2. Создайте Spring бин, который преобразует входящий SAML assertion в объект пользователя Jmix. Самый простой способ — расширить BaseSamlUserMapper и переопределить его методы:

    @Component
    public class MySamlUserMapper extends BaseSamlUserMapper<MyUser> {
    
        @Autowired
        protected SamlAssertionRolesMapper rolesMapper;
    
        @Override
        protected MyUser initJmixUser(Assertion assertion) { (1)
            return new MyUser();
        }
    
        @Override
        protected void populateUserAttributes(Assertion assertion, OpenSaml4AuthenticationProvider.ResponseToken responseToken, MyUser jmixUser) { (2)
            Map<String, List<Object>> assertionAttributes = SamlAssertionUtils.getAssertionAttributes(assertion);
            List<Object> rawValues = assertionAttributes.get("Position");
            String positionValue = CollectionUtils.isNotEmpty(rawValues) ? rawValues.get(0).toString() : null;
            jmixUser.setPosition(positionValue);
            System.out.println(positionValue);
        }
    
        @Override
        protected void populateUserAuthorities(Assertion assertion, MyUser jmixUser) { (3)
            Collection<? extends GrantedAuthority> grantedAuthorities = rolesMapper.toGrantedAuthorities(assertion);
            jmixUser.setAuthorities(grantedAuthorities);
        }
    }
    1 Этот метод создает объект пользователя Jmix, который будет представлять аутентифицированного пользователя.
    2 Здесь значения из SAML Assertion копируются в ваш объект пользователя. В примере атрибут Position из Assertion сохраняется в поле position объекта MyUser.
    3 В этом методе пользователю назначаются authorities. Вместо прямой реализации этой логики, пример делегирует ее интерфейсу SamlAssertionRolesMapper и, следовательно, реализации по умолчанию DefaultSamlAssertionRolesMapper.

Использование другого атрибута для ролей

По умолчанию DefaultSamlAssertionRolesMapper ищет атрибут с именем Role в SAML Assertion. Ожидается, что этот атрибут содержит коллекцию имен ролей. Для каждого имени роли Jmix ищет ресурсные роли и роли уровня строк с соответствующими кодами. При совпадении кода – соответствующие разрешения добавляются пользователю.

Если ваш провайдер отправляет роли в атрибуте, отличном от Role, вы можете указать необходимый атрибут с помощью следующего свойства Jmix SAML:

application.properties
jmix.saml.default-saml-assertion-roles-mapper.roles-assertion-attribute=MyRole

Маппер по умолчанию будет читать имена ролей из MyRole вместо Role.

Создание собственного маппера ролей

Если требуется больше контроля, вы можете создать собственный маппер ролей. Это полезно, когда провайдер не отправляет коды ролей Jmix напрямую, а вместо этого отправляет другие значения, которые вы хотите преобразовать в подходящие роли.

Например, значение Manager может приходить из атрибута Position, и ваш маппер может использовать его для назначения подходящих ролей Jmix. Для этого расширьте BaseSamlAssertionRolesMapper и переопределите методы getResourceRolesCodes() и getRowLevelRolesCodes():

@Component
public class MySamlAssertionRolesMapper extends BaseSamlAssertionRolesMapper {

    @Override
    protected Collection<String> getResourceRolesCodes(Assertion assertion) {
        Map<String, List<Object>> assertionAttributes = SamlAssertionUtils.getAssertionAttributes(assertion);
        List<Object> rawPositionAttributeValues = assertionAttributes.get("Position");

        Collection<String> jmixRoleCodes = new HashSet<>();
        rawPositionAttributeValues.stream()
                .map(Object::toString)
                .forEach(position -> {
                    if ("Manager".equals(position)) {
                        jmixRoleCodes.add("edit-contracts");
                        jmixRoleCodes.add("view-archive");
                    } else {
                        jmixRoleCodes.add("view-contracts");
                    }
                });

        return jmixRoleCodes;
    }

    @Override
    protected Collection<String> getRowLevelRoleCodes(Assertion assertion) {
        // Do something for row-level role codes
        return List.of();
    }
}

В этом примере значение Position пользователя определяет, какие ресурсные Jmix будут назначены:

  • если Position содержит значение Manager, пользователь получает роли edit-contracts и view-archive.

  • в противном случае пользователь получает роль view-contracts.

Сохранение пользователей в базе данных

По умолчанию конфигурация Jmix SAML хранит аутентифицированных пользователей только в памяти. Если вы хотите, чтобы пользователи SAML сохранялись в базе данных, выполните следующие шаги:

  1. Сделайте сущность User совместимой с дополнение Jmix SAML, расширив абстрактный класс JmixSamlUserEntity:

    @JmixEntity
    @Entity
    @Table(name = "USER_", indexes = {
            @Index(name = "IDX_USER__ON_USERNAME", columnList = "USERNAME", unique = true)
    })
    public class User extends JmixSamlUserEntity implements JmixUserDetails, HasTimeZone {
    
        //...
    
    }
  2. Зарегистрируйте маппер пользователей на основе SynchronizingSamlUserMapper; этот суперкласс сохраняет и обновляет пользователя в базе данных, а также может синхронизировать назначения ролей с базой данных:

    @Component
    public class MySynchronizingSamlUserMapper extends SynchronizingSamlUserMapper<User> {
    
        public MySynchronizingSamlUserMapper() {
            super();
            setSynchronizeRoleAssignments(true); (1)
        }
    
        @Override
        protected Class<User> getApplicationUserClass() {
            return User.class;
        }
    
        @Override
        protected void populateUserAttributes(Assertion assertion, OpenSaml4AuthenticationProvider.ResponseToken responseToken, User jmixUser) {
            String username = SamlAssertionUtils.getUsername(assertion);
            Map<String, List<Object>> assertionAttributes = SamlAssertionUtils.getAssertionAttributes(assertion);
            String firstNameValue = getStringAttributeValue(assertionAttributes, "FirstName", username);
            String lastNameValue = getStringAttributeValue(assertionAttributes, "LastName", username);
    
            jmixUser.setUsername(username);
            jmixUser.setFirstName(firstNameValue);
            jmixUser.setLastName(lastNameValue);
        }
    
        protected String getStringAttributeValue(Map<String, List<Object>> assertionAttributes, String attributeName, String username) {
            List<Object> rawValues = assertionAttributes.get(attributeName);
            return CollectionUtils.isNotEmpty(rawValues)
                    ? rawValues.get(0).toString()
                    : "%s (%s)".formatted(attributeName, username);
        }
    }
    1 Если установлено значение true, назначения ролей также сохраняются в базе данных.