EITCO User Management Service
The User Service provides a user administration designed for different applications and systems. It is a database-based user administration.
User directories can also be connected via the Lightweight Directory Access Protocol (LDAP). Such a connection supports read synchronization regarding the user data of the user directory. Reading means that the user service reads the user data from the user directory and creates or modifies the corresponding user service user data for this purpose, but conversely does not write any data to the user directory.
Closely related to user administration is the issue of authentication and authorization. In this respect, the user service is a supporting service that offers functionalities such as storing the password in encrypted form or managing roles and rights.
For authentication and authorization, the Spring security framework would have to be used in addition to the user service, if necessary in conjunction with an authentication component. The concrete authentication and authorization scenario depends on the respective system context. Custom developments can also be integrated for authentication. As a note, it should be mentioned here that an adequate ACL implementation for access protection of concrete application objects is offered neither by the user service nor by Spring Security. This would require further solutions.
Basic functions
The user administration supports the creation, modification and deletion of:
-
users
-
user groups
-
roles
-
tokens
-
privileges
A token (or authority) is a loose definition of a right. By assigning such a loose definition of a right to a role, the role is granted a privilege. By means of a token-to-role assignment, a privilege can in turn also be withdrawn from the corresponding users.
Data model
The following figure outlines the relationship between users, roles, etc. based on the database tables of the user service.
Overview of the database tables
table | purpose | sample data set |
---|---|---|
usrv_user |
contains the user records |
- |
usrv_user_extra_meta |
contains the metadata of freely definable user data fields that supplement the fields of the usrv_user table |
additional field date of birth: (1, DATE OF BIRTH, DATETIME, 0, "This is the…") |
usrv_user_extra |
contains the values of the freely definable user data fields |
String value record: (1, 1, 5, 1, NULL, NULL, NULL, "foo value") |
usrv_group |
Contains the user group records |
- |
usrv_group_extra_meta |
contains the metadata of user-definable group data fields that complement the fields of the usrv_group table |
- |
usrv_group_extra |
contains the values of the freely definable user group data fields |
- |
usrv_group_to_group |
contains the records of the user group hierarchy |
- |
usrv_user_to_group |
contains the user group mapping records |
- |
usrv_role |
contains the role records. The fields of such a record are listed in the following table |
- |
usrv_user_to_role |
contains the user role assignment records |
- |
usrv_group_to_role |
contains the user group role mapping records |
- |
sec_token |
contains the token or rights data records |
- |
sec_privilege |
contains the role token mapping records |
- |
Integration of the user service artifacts
The user service can be used as a standalone component in the sense of a microservice, or it can be integrated directly into the context of the actual application.
Direct integration into the context of the actual application requires that the integration takes place within a Spring Boot context.
Deployment as a microservice, on the other hand, is done in a distributed microservice environment using the components recommended by Spring Cloud. The microservice itself is a Spring boot application (server). To use the microservice within a Spring Cloud infrastructure, the user service provides a Java-based microservice client component to be used by the clients of the microservice. By using this client component, load balancing etc. are automatically provided. Like the direct integration, the integration of the client component requires that the integration takes place within a Spring boot context.
Both direct integration and integration as a microservice client provide the same application programming interface (API). It should be noted, however, that microservice calls cannot be included in a transaction context that is open on the caller’s side.
Dependencies
Integration of the Microservice client
<dependency>
<groupId>de.eitco.commons</groupId>
<artifactId>cmn-user-management-client-spring-boot-starter</artifactId>
<version>6.1.2</version>
</dependency>
compile 'de.eitco.commons:cmn-user-management-client-spring-boot-starter:6.1.2'
Direct integration (embedded)
<dependency>
<groupId>de.eitco.commons</groupId>
<artifactId>cmn-user-management-embedded-spring-boot-starter</artifactId>
<version>6.1.2</version>
</dependency>
compile 'de.eitco.commons:cmn-user-management-embedded-spring-boot-starter:6.1.2'
Gradle-Dependency
User service configurations
The following is an example of the user service configuration using a Spring YAML configuration file.
spring:
# The configuration of a datasource is not user service specific; it should only be pointed out that
# that the user service requires a datasource.
datasource:
url: jdbc:postgresql://localhost:5432/postgres?currentSchema=foo
username: foouser
password: foopassword
driver-class-name: org.postgresql.Driver
user-service:
group:
# a user group is to be typified; possible types can be
# deposited comma-separated at this point
#
# If the type "Standard" is specified, no type must be specified when saving a user group record.
# In such a case the type "Standard" will be set automatically.
#
types: Standard, OU, Job
#
# A user group can have freely definable attributes assigned to it; such
# definitions can be made here in a semicolon-separated way.
# The values of a definition are comma-separated.
# The first value specifies the attribute name, the second the type and
# the third, whether the attribute value is a mandatory value.
# Possible types are: boolean, decimal, long, datetime, string, blob;
#
extra-fields: selectable,boolean,false;fooDate,datetime,false;
user:
# Specify the superuser based on a comma-separated list of user names; if the value -dbFlag- is entered here,
# then the superuser will be determined by a flag in the
# user database table
#
superuser: -dbFlag-
#
# A user can be assigned freely definable attributes.
# further description see above under user-service.group.extra-fields
extra-fields: fooDecimal,decimal,false;fooString1,string,false;fooString2,string,false
auth:
# The user service supports the integration of Spring security. With the following
# configuration you can specify the implementation of the Spring Spring-Security-Userdetail-Interfaces.
# (Default is de.eitco.commons.user.management.common.auth.UserDetailsDB)
#
user-details-path: de.eitco.commons.user.management.common.auth.UserDetailsDB
#
# The following configuration specifies which key is used to identify the user
# to whom the request is assigned via request token is to be
# determined by the user service. The request token can contain the user name or the external
# user ID. Possible configuration values are NAME or EXTERNAL_ID (default is NAME).
#
user-identification-field: NAME
Auditing
The User Management Service supports auditing of changes made to the user management data. The auditing support must be
activated by setting user-service.audit-support-enabled=true
in the service’s configuration. Auditing can then be enabled by creating audit rules for the tables to be audited. For example, to audit changes made to users an audit rule with tableName=usrv_user
must be created. The rule’s settings for deleteUserIdField
, deleteUserIdSetting
, updateUserIdField
and updateUserIdSetting
can be left empty to use the default values. The documentation of the
audit service contains more information on how to create audit rules.
API Overview
The user service contains so-called managers in the form of interfaces and their implementations for user administration. The central manager class or interface is de.eitco.commons.user.management.common.manager.CrudManager. By means of this interface the methods for creating, reading, changing and deleting records are declared (CRUD = Create, Read, Update, Delete). The following CRUD methods are declared in it:
-
T getEntity(Long entityId) - read a record based on the unique record ID.
-
QueryResult<T> listEntities(ServiceQuery query) - read records based on filtering, if any, by means of a query
-
T saveEntity(T entity) - create and modify a record
-
void deleteEntity(Long entityId) - delete a record based on the unique record ID
Other managers are assignment managers to assign the corresponding entities. For example, assigning a user to a user group. There is also the AuthManager which is described in the API Security chapter.
To manage the data using the CRUD Manager, model classes are declared in the form of POJOs in each case. These are:
-
de.eitco.commons.user.management.common.model.UserGroup
-
de.eitco.commons.user.management.common.model.UserRole
-
de.eitco.commons.user.management.common.model.SecToken
-
de.eitco.commons.user.management.common.model.SecPrivilege
-
de.eitco.commons.user.management.common.model.User
Create, modify and delete a user
@Autowired
UserManager userMgr;
User user = new User();
user.setUsername("user@foo.foo");
user = userMgr.saveEntity(user);
@Autowired
UserManager userMgr;
// Read user record by means of e.g. getEntityByUsername()
User user = userMgr.getEntityByUsername("user@foo.foo");
// change email address
user.setEmail("user@foo.foo");
user = userMgr.saveEntity(user);
@Autowired
UserManager userMgr;
// Delete existing user by system ID
userMgr.deleteEntity(user.getId());
Create, modify and delete a role, group, token, privilege
Creating, changing and deleting roles, groups, tokens and privileges is done in the same way as described in the previous section for users. For this, of course, the respective manager and the corresponding POJO must be used. In the case of roles, for example, this would be the UserRole manager and the UserRole POJO.
Assignment management
For assignment management, the assignment managers are used. These are:
-
UserToUserGroupManager - manager to assign users to groups.
-
UserRoleToUserManager - manager to assign roles to users.
-
UserRoleToGroupManager - manager to assign roles to groups.
-
UserGroupTreeManager - manager to assign groups to groups (in the sense of a tree or hierarchy).
-
(SecPrivilegeManager - The SecPrivilegeManager is to be used for the assignment of tokens to roles).
The following code examples show the four essential methods (add, remove, list a and list b) of the UserToUserGroupManager, UserRoleToUserManager and UserRoleToGroupManager based on the UserToUserGroupManager.
@Autowired
UserToUserGroupManager userToGroupMgr;
// such assignment is done by user id and group id
userToGroupMgr.addUserToGroup(userId, groupId);
@Autowired
UserToUserGroupManager userToGroupMgr;
// remove user from group is done using user id and group id
userToGroupMgr.removeUserFromGroup(userId, groupId);
@Autowired
UserToUserGroupManager userToGroupMgr;
// Note: method accesses the database directly; so no cache usage.
List<User> userToGroupMgr.listUsersOfGroup(Long groupId);
@Autowired
UserToUserGroupManager userToGroupMgr;
// Note: method accesses the database directly; so no cache usage
List<UserGroup> userToGroupMgr.listGroupsOfUser(Long userId);
Create and modify group hierarchies
The creation and modification of group hierarchies are supported by the methods of the UserGroupToGroupManager. Following are code examples for this.
@Autowired
UserGroupToGroupManager userGroupToGroupMgr;
// add a subgroup to a group
groupToGroupMgr.addSubGroup(groupId, subGroupId);
@Autowired
UserGroupToGroupManager userGroupToGroupMgr;
// remove a subgroup from a group
groupToGroupMgr.removeSubGroup(groupId, subGroupId);
@Autowired
UserGroupToGroupManager userGroupToGroupMgr;
// list the direct subgroups of a group
List<UserGroup> directSubGroupList = groupToGroupMgr.getDirectSubGroups(Long groupId);
// list the direct subgroups of a group
List<UserGroup> directSuperGroupList = groupToGroupMgr.getDirectSuperGroups(Long groupId);
Role/privilege inheritance through the group hierarchy
Queries using service query
Using a service query implementation, simple database queries can be created and executed, which are usually sufficient for entity management. A service query is based on a root entity (user, group, role, privilege or token).
In a concrete query formulation, the occurring select, filter and sort attributes are to be marked with the corresponding entity prefix (see also examples below). The following prefixes are defined:
Entity | Prefix |
---|---|
Group |
g |
GroupExtra (Extension of the Group entity - freely definable attributes) |
ge |
Privilege |
p |
Role |
r |
Token |
t |
User |
u |
UserExtra (Extension of the User entity - freely definable attributes) |
ue |
In the following example the u.username identifies the username attribute of the User entity.
@Autowired
UserManager userMgr;
// Create ServiceQuery instance
ServiceQuery serviceQuery = userMgr.newQuery();
// RETRIEVE ALL USERS WITH ALL DIRECT AND FREELY DEFINABLE ATTRIBUTES
// call the listEntities method based on the created ServiceQuery instance;
// in the simple case, i.e. without specifying any select, filter or sort attributes
// all users with all direct and freely definable attributes are returned; in
// such a case, null could also be passed instead of the ServiceQuery instance
QueryResult<User> userResult = userMgr.listEntities(serviceQuery);
// The QueryResult instance contains the result list.
List<User> userList = userResult.getList();
// SELECT SPECIFIC ATTRIBUTES
serviceQuery .addSelect("u.username", "ue.foo", "r.name");
// FILTER BY SPECIFIC ATTRIBUTES
serviceQuery.addFilterEq("u.username", "mustermann"); // exact match filter
serviceQuery.addFilterEq("u.username", "mus", ServiceQuery.TXT_MATCH_STYLE_STARTS_WITH) // starts with match filter
serviceQuery.addFilterEq("u.username", "uster", ServiceQuery.TXT_MATCH_STYLE_SUBSTRING) // substring match filter
serviceQuery.addFilterEq("ue.fooDate", myDate); // equals filter
serviceQuery.addFilterEq("ue.fooLong", 1L); // equals filter
serviceQuery.addToOrFilterEq("u.firstname", "Erika");
serviceQuery.addToOrFilterEq("u.firstname", "Max") // exact match Erika or Max
serviceQuery.addFilterIn("ue.fooLong", myLongCollection); // in condition
etc.
// SORT BY SPECIFIC ATTRIBUTES
serviceQuery.addOrderBy("u.username", ServiceQuery.SORT_DESC); // sort in descending order
etc.
// START-ROW, END-ROW and COUNT
serviceQuery.setStartRow(0); // "start row" included
serviceQuery.setEndRow(10); // "end row" not included (compare Java substring method)
serviceQuery.withCount(); // in addition to the partial hit list "start/end" the actual total hit count
// of the formulated query, which is then supplied in the result when calling withCount
The above examples for querying users also apply accordingly to Role, Group, Privilege and Token.
Create, assign and modify events
User Service Events serve to implement their own logic before, during or after actions of the User Service. Three types of events have been defined for this purpose:
-
Before - Before an action of the user service
-
On - During an action of the User Service
-
After - After an action of the User Service.
@Async
@EventListener
public void handleEvent(BeforeGroupCreateEvent event) {
// do something
}
With the help of the Async annotation, the method Asynchronous is called. By default, the methods are called synchronously.
- NOTE
-
At this stage, all contexts are lost when the method is called using the Async annotation (e.g. tenant context etc.). To be able to call an event, the annotation EventListener must always be specified above the method. The desired logic can then be implemented within the method.
API Lookup / Cache
The user service provides a lookup implementation. It can be used for queries whether a user is a member of a certain group, or whether a user is assigned a certain role, etc. In contrast to the queries of the manager classes, which access the database directly, the queries via lookup implementation are based on a cache implementation.
//---------------------------------------------------------------------
// Subsequent queries whether a user is superuser, is in role x, etc.
//---------------------------------------------------------------------
AuthLookup.isSuperuser();
AuthLookup.isSuperuser(String username);
AuthLookup.isInRole(String roleName);
AuthLookup.isInRole(String username, String roleName);
AuthLookup.isInGroup(String groupShortname);
AuthLookup.isInGroup(String username, String groupShortname);
AuthLookup.isPrivilegeGranted(String tokenName);
AuthLookup.isPrivilegeGranted(String username, String tokenName);
AuthLookup.areAllPrivilegesGranted(String... tokenNameList);
AuthLookup.areAllPrivilegesGranted(String username, String... tokenNameList);
AuthLookup.isOnePrivilegesGranted(String...tokenNameList),
AuthLookup.isOnePrivilegeGranted(String username, String...tokenNameList),
AuthLookup.isPrivilegeGrantedGroup(String tokenName, String groupShortname);
AuthLookup.isPrivilegeGrantedGroup(String username, String tokenName, String groupShortname);
....
//-----------------------------------------------------------------------------------------------
// Following lookup queries for a user's group, role, etc. listings.
//
// The following lookup methods are not intended for user management. For this purpose the
// managers should be used for that. The lookup methods are intended to support the formulation of corresponding queries
// such as e.g., the formulation of ACL queries, to support e.g., joins
// related to e.g. group hierarchies.
//
// If the user is a superuser, then the following methods return null; otherwise
// always return a collection, i.e., also an empty collection.
//-----------------------------------------------------------------------------------------------
Collection<String> roleNameList = AuthLookup.listRoles();
Collection<String> roleNameList = AuthLookup.listRoles(String username);
Collection<String> groupShortnameList = AuthLookup.listGroups();
Collection<String> groupShortnameList = AuthLookup.listGroups(String username);
Collection<String> tokeNameList = AuthLookup.listPrivilegesGranted();
Collection<String> tokeNameList = AuthLookup.listPrivilegesGranted(String username);
Collection<String> tokeNameList = AuthLookup.listPrivilegesGrantedGroup(String groupShortname);
Collection<String> tokeNameList = AuthLookup.listPrivilegesGrantedGroup(String username, String groupShortname);
// the current user can be retrieved via UserContext
User user = UserContext.getCurrentUser();
For authentication/authorization: The current user is determined using the Spring Security context. so this must be set accordingly by means of e.g. servlet filters. The following is the current implementation of the user service to determine the current user. |
public static User getCurrentUser() {
SecurityContext secCtx = SecurityContextHolder.getContext();
if (secCtx == null) {
// using the user context of the user service a user can also be set independently of the
// security context; e.g. to set backend job executions // within a user context.
// within a user context; this context is returned with getUser()
return getUser();
}
Authentication auth = secCtx.getAuthentication();
if (auth == null) {
return getUser();
}
if (auth instanceof CommonAuthentication) {
UUID userIdentifier = ((CommonAuthentication) auth).getUserIdentifier();
UserManager userManager = BeanLookup.getBean(UserManager.class);
return userManager.getEntityByUuid(userIdentifier.toString());
}
Object principal = auth.getPrincipal();
if (principal instanceof UserDetails) {
// Note: The user service provides a user-details implementation that can be used
// can be used to create an authentication instance.
return ((UserDetails) principal).getUser();
}
return UserContext.getUser();
}
Cache implementation
This document is based on the user service version 1.2. The cache implementation used for the above lookup methods is Infinispan. To use the Infinispan cache, the following Spring configuration and the Infinispan configuration itself must be specified with respect to the user service.
# Spring configuration: reference to the actual infinispan configuration file.
# This reference can be a direct reference to the file system or an indirect one
# via classpath.
# See also: 'https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-caching-provider-infinispan'
infinispan:
embedded:
configXml: infinispan.xml
The minimum infinispan configuration for the user service is listed below. Either a configuration for a local so-called simple cache (name: 'user-cache-local') or a configuration for a distributed cache ('user-cache-cluster') in the form of an invalidation cache (see also https://infinispan.org/docs/stable/user_guide/user_guide.html) must be specified.
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:infinispan:config:9.4 http://www.infinispan.org/schemas/infinispan-config-9.4.xsd"
xmlns="urn:infinispan:config:9.4">
<!--
Notes for the following expiration configuration:
lifespan, interval and max-idle in millis
lifespan: the time that an entity is kept in the cache before it is evicted
max-idle: the idle time of an entity before it is evicted; -1 means ignoring max-idle
interval: the check period for eviction
-->
<cache-container>
<!-- - - - - - - - - - - - - - - - - - - - -->
<!-- simple cache for single node systems -->
<!-- - - - - - - - - - - - - - - - - - - - -->
<local-cache-configuration name="user-cache-local" simple-cache="true">
<expiration lifespan="43200000" interval="300000" max-idle="-1"/>
</local-cache-configuration>
<!-- - - - - - - - - - - - - - - - - - - - - -->
<!-- invalidation cache for cluster systems -->
<!-- - - - - - - - - - - - - - - - - - - - - -->
<!-- <transport cluster="cmn-cache-cluster"/> -->
<!-- <invalidation-cache-configuration name="user-cache-cluster" mode="ASYNC"> -->
<!-- <expiration lifespan="43200000" interval="300000" max-idle="-1"/> -->
<!-- </invalidation-cache-configuration>-->
</cache-container>
</infinispan>
Alternative cache implementation
In order to use a different cache implementation than Infinispan, the following entry must be made in the yaml configuration:
spring:
cache:
type: CUSTOM
If the cache configuration is missing, then Infinispan is used; Infinispan can also be configured explicitly as follows (however, this is optional):
spring:
cache:
type: INFINISPAN
If the cache type CUSTOM is configured, a bean of the type de.eitco.commons.user.management.common.cache.CustomCacheManager is expected in the Spring application context, defined as follows:
@Bean
@Conditional(CustomCacheManagerChecker.class)
public CustomCacheManager customCacheManager() {
return new MyCacheManager();
}
The conditional annotation is not necessary; however, if the cache type is not CUSTOM, then the annotation will not create the bean. |
The following is the CustomCacheManager interface:
public interface CustomCacheManager {
Cache getCache(String cacheName);
Cache getCache(String cacheName, @Nullable Map<String, Object> cacheConfiguration);
}
So a bean that implements the CustomCacheManager is to be provided in the application context.
The return value Cache in the interface methods is org.springframework.cache.Cache.
Securing REST Endpoints
Spring security should be used to secure the REST endpoints. Below is a brief description of such a protection. Of course, it is recommended to look at the numerous documentations about Spring-Security on the Internet to get more detailed information about this topic.
In order to use Spring-Security, it has to be activated. The activation can be done in a Spring boot application by a configuration bean that is annotated with the annotation @EnableWebSecurity. Such a configuration bean extends the Spring Security class WebSecurityConfigurerAdapter, as shown in the following example.
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
}
As an artefact, 'spring-boot-starter-security' is to be included (see the Maven dependency below).
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
To secure the endpoints, the configure method of the WebSecurityConfigurerAdapter class must now be overwritten using the configuration bean (see following example).
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/public/**").permitAll()
.antMatchers("/foo2/**").hasRole("ADMIN")
.antMatchers("/foo3").hasAuthority("ERSTELLEN")
.antMatchers("/foo4/**").hasAnyRole("ADMIN", "FOO")
.antMatchers("/foo/foo5").hasAnyAuthority("ERSTELLEN","LOESCHEN")
.and()...;
}
}
By means of the method authorizeRequests, the specification of Apache-Ant-Patterns is initiated, i.e. the specification of the URL path patterns to be secured or not to be secured. If nothing is specified, authentication is required for each URL path or endpoint. Otherwise, in case of ambiguous specification, the order is decisive. The first specification is valid.
A possible context part of the URL path is not to be specified. For example, at http://host:port/fooContext/foo2, only /foo2 would have to be specified.
Note on hasRole and hasAuthority: Spring internally only knows a list of authorities. A role is now determined by the fact that the authority name begins with ROLE_. The user service automatically prefixes the authorities with ROLE_ when compiling them to pass them to Spring-Security.
Note: What else is or can be configured by means of the HttpSecurity instance can be found at https://docs.spring.io/spring-security/site/docs/5.1.5.RELEASE/api/org/springframework/security/config/annotation/web/builders/HttpSecurity.html, for example.
Endpoints can also be secured with an endpoint method by means of a method annotation. To do this, the configuration bean must also be provided with the annotation @EnableGlobalMethodSecurity.
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
}
The annotation @EnableGlobalMethodSecurity activates, as the name says, globally corresponding method annotations. These are basically all methods. So not only methods of the REST endpoints. In the example above, this activates the PreAuthorize annotation, among others, which is supported by the user service, see as follows.
@RequestMapping(value = "/forms", method = RequestMethod.POST,. . .
@PreAuthorize("@authService.isPrivilegeGranted('UPLOAD')")
@ResponseBody
public ResponseEntity<ServiceReply> uploadForm(@RequestBody final FormRequest request) {
}
By means of the method annotation @PreAuthorize, the methods specified above under API Lookup/Cache can be used in relation to the current user. Listed again are the following methods: isSuperuser(), isInRole(String), isInGroup(String), isPrivilegeGranted(String), areAllPrivilegesGranted(String…), isOnePrivilegeGranted(String…), isPrivilegeGrantedGroup(String, String). For more information, refer to the API documentation for de.eitco.commons.user.management.common.auth.AuthService.
Client Management
tenant:
# The following configurations are used to define three clients as examples. To configure a client,
# the access parameter values of the respective client database are to be specified.
#
# Note: In the absence of a client configuration, the database access parameters specified under spring.datasource apply.
# In such a case, the user service is started under the quasi-client "master".
tenants:
- tenant-id: TenantFoo1
db-url: jdbc:postgresql://localhost:5432/?currentSchema=usersrv
db-username: postgres
db-password: manage
db-driver-class-name: org.postgresql.Driver
- tenant-id: TenantFooDemo1
db-url: "jdbc:h2:mem:test;MODE=PostgreSQL;MVCC=true;LOCK_TIMEOUT=15000;DB_CLOSE_DELAY=-1"
db-username: sa
db-password:
db-driver-class-name: org.h2.Driver
- tenant-id: TenantFooDemo2
db-url: "jdbc:h2:file:~/.h2/user;AUTO_SERVER=TRUE;MODE=PostgreSQL"
db-username: foo
db-password: bar
db-driver-class-name: org.h2.Driver
LDAP Synchronization
user-service:
ldap:
tenants:
- tenant-id: master
server-urls:
- ldap://eit-bln-svdom1.eitco.de:389
auth-user-name: tpruefen@eitco.de
auth-user-password: changeit
date-format: yyyyMMddHHmmss
user-search-bases:
- ou=mitarbeiter,ou=bremen,ou=eitco,dc=eitco,dc=de
- ou=mitarbeiter,ou=bonn,ou=eitco,dc=eitco,dc=de
- ou=mitarbeiter,ou=berlin,ou=eitco,dc=eitco,dc=de
user-filter: objectclass=person
user-mappings:
- username, sAMAccountName
- externalId, objectGUID;binary
- email, mail
- firstname, givenName
- lastname, sn
- tmpList-memberOf, memberOf
group-search-bases:
- cn=Builtin,dc=eitco,dc=de
- cn=Users,dc=eitco,dc=de
- ou=gruppen,ou=eitco,dc=eitco,dc=de
group-filter: objectclass=group
group-mappings:
- shortname, sAMAccountName
- externalId, objectGUID;binary
- longname, cn
- tmpList-memberOf, memberOf
Unit testing
The User Service can be used to set a user context for unit tests. It is also possible to use the Spring Security on-board tools, such as the WithMockUser annotation. However, the full range of functions of the User Service is not available with the on-board tools and, in addition, they cannot be used in part in connection with a Spring boot test. Therefore, it is recommended to use the user service itself to set a user context. For this purpose, the following dependency must be specified for Maven and Gradle:
Maven-Dependency: .Integration of user service and test users
<dependency>
<groupId>de.eitco.commons</groupId>
<artifactId>cmn-user-management-test-with-user</artifactId>
<version>entsprechende Version</version>
<scope>test</scope>
</dependency>
Gradle-Dependency:
test 'de.eitco.commons: cmn-user-management-test-with-user:<yourVersion>'
WithTestUser annotation
The @WithTestUser annotation has two parameters. These are the value parameter and the resetData parameter. The value parameter is used to define the user context and the resetData parameter is used to indicate whether all user, group, role and privilege data should be deleted before setting up the corresponding user context.
The value parameter contains the user context definitions in comma-separated form. The values of a user definition always contain a prefix. The following table lists the possible definitions.
prefix | description |
---|---|
g |
Specification of a group to be assigned to the user (e.g. g.fooGroup.oe, i.e. the group fooGroup of type oe; note: each group is to be typed OU, job, team etc.). |
gfg |
Indication of a group which can be indirectly assigned to the user; indirectly via group hierarchy (e.g. gfg. fooGroupParent.oe, i.e. the group fooGroupParent of type oe; assignment see next line via prefix sg) |
p |
Assignment of a privilege to a role (e.g. p.AKTE_ERSTELLEN.fooRole, i.e. the privilege AKTE_ERSTELLEN, which is assigned to the role fooRole). Using the prefix, an explicit non-granting of a privilege can also be defined. For this purpose, a notGranted must be appended to the definition (e.g. p.ACTE_ERSTELLEN.fooRole.notGranted). rg Specification of a role that is assigned to a group (e.g. rg.fooRoleGroup.fooGroupParent, i.e. the role fooRoleGroup assigned to the group fooGroupParent) |
ru |
Specification of a role that is assigned to the user (e.g. ru.fooRole, i.e. the role fooRole) |
sg |
Assignment of a subgroup to a group (e.g. sg.fooGroup.fooGroupParent, i.e. the group fooGroup is assigned to the group fooGroupParent as a subgroup) |
u |
Specification of the user (e.g. u.fooUser defines the user fooUser). Exactly one user definition must be specified. By means of the postfix superuser, the user becomes the superuser (e.g. u.fooUser.superuser). |
Client context
The client context of a test class or a test method can be set with the @WithTenant annotation. If no annotation is set, the master tenant is automatically used. For this purpose, the FullTestExecutionListener must be set as TestExecutionListener by means of the class annotation TestExecutionListeners (even if the annotation is not to be used).
Setup import
The setup import is used to import configured tokens, roles, groups, users and their assignments. In the same way, OAuth client configurations can be imported via setup import.
The configurations to be imported are to be stored as Spring Boot configurations.
The import takes place during the start of a standalone user service instance or during the start of an application instance in which the user service is embedded.
Token import
The following exemplary YAML configuration shows the structure of a configuration for the token import. The two tokens fooToken1 and fooToken2 are defined.
To set a token value, the name of the SecToken pojo attribute is to be used in the configuration. |
user-service:
config-data:
tenants:
# Setup imports are configured on a client-by-client basis. The client ID "all" means that the
# configuration applies to all clients. The client ID "all" must also be specified if
# no client has been explicitly configured. This is because the application then starts under the so-called
# default client.
- tenant-id: all
token:
- type: function
path: fooToken1
description: This is...
# the token is assigned to the role fooRole1 as guaranteed
privilegeGrantedToRoles: fooRole1
# the token is assigned to the role fooRole2 as not guaranteed;
# such an assignment is relevant in relation to group hierarchies,
# to revoke a token (privilege) granted by the hierarchy.
privilegeNotGrantedToRoles: fooRole2
- type: function
path: fooToken2
description: This is...
# the token is assigned to the two roles fooRole1 and fooRole
privilegeGrantedToRoles: fooRole1,fooRole2
Role Import
The following exemplary YAML configuration shows the structure of a configuration for role import. The two roles fooRole1 and fooRole2 are defined.
To set a role value, the name of the UserRole pojo attribute must be used in the configuration. |
user-service:
config-data:
tenants:
# see explanation of tenant-id for token import
- tenant-id: all
roles:
- name: fooRole1
description: This is…
- name: fooRole2
description: This is…
Group import
The following exemplary YAML configuration shows the structure of a configuration for the group import. The two groups fooGroup1 and fooGroup2 are defined.
To set a group value, the name of the UserGroup pojo attribute must be used in the configuration. |
user-service:
config-data:
tenants:
# see explanation of tenant-id for token import
- tenant-id: all
groups:
# z.B. GUID from Active Directory
- externalId: C7FE1854-9428-44B6-9A77-3FADE10C1BCF
# z.B. DN from Active Directory
externalPath: groups.employee
type: Standard
shortname: fooGroup1
longname: fooGroup1Longname
division: fooGroup1Division
area: fooGroup1Area
enabled: true
escription: This is...
# the role fooRole1 is assigned to the group
memberOfRoles: fooRole1
- externalId: D7FE1854-9428-44B6-9A77-3FADE10C1BCF
externalPath: groups.developer
type: Standard
shortname: fooGroup2
longname: fooGroup2Longname
division: fooGroup2Division
area: fooGroup2Area
enabled: true
description: This is...
# the group is assigned to the roles fooRole1 and fooRole2
memberOfRoles: fooRole1,fooRole2
# the group is classified as a subgroup of the group fooGroup1
subGroupOfGroups: fooGroup1
User Import
The following exemplary yaml configuration shows the structure of a configuration for the user import. The user fooUser1 is defined.
To set a user value, the name of the user pojo attribute must be used in the configuration. |
user-service:
config-data:
tenants:
# see explanation of tenant-id for token import
- tenant-id: all
users:
- uuid: 822bd19a-6104-4cae-a7ef-8380c1e8e6c5
externalId: B7FE1854-9428-44B6-9A77-3FADE10C1BCF
username: fooUser1
password: fooPassword
title: Dr.
salutation: Mrs.
lastname: Mustermann
firstname: Erika
email: emustermann@foo.foo
superuser: false
validFrom: 2000-01-01T00:00:00Z
validUntil: 2050-12-31T00:00:00Z
enabled: true
description: This is…
# the user is assigned to the groups fooGroup1 and fooGroup2
memberOfGroups: fooGroup1,fooGroup2
# the user is assigned to the roles fooRole1 and fooRole2
memberOfRoles: fooRole1,fooRole2