Chapter 4. Spring Security with Red Hat Decision Manager Spring Security is provided by a collection of servlet filters that make up the Spring Security library . These filters provide authentication through user names and passwords and authorization through roles. The default Spring Security implementation generated in a Red Hat Decision Manager Spring Boot application provides authorization without authentication. This means that anyone with a user name and password valid for the application can access the application without a role. The servlet filters protect your Spring Boot application against common exploits such as cross-site request forgery (CSRF) and cross-origin resource sharing (CORS). Spring Web relies on the DispatcherServlet to redirect incoming HTTP requests to your underlying java REST resources annotated with the @Controller annotation. The DispatchServlet is agnostic of elements such as security. It is good practice and more efficient to handle implementation details such a security outside of the business application logic. Therefore, Spring uses filters to intercept HTTP requests before routing them to the DispatchServlet . A typical Spring Security implementation consists of the following steps that use multiple servlet filters: Extract and decode or decrypt user credentials from the HTTP request. Complete authentication by validating the credentials against the corporate identity provider, for example a database, a web service, or Red Hat Single Sign-On. Complete authorization by determining whether the authorized user has access rights to perform the request. If the user is authenticated and authorized, propagate the request to the DispatchServlet . Spring breaks these steps down into individual filters and chains them together in a FilterChain. This chaining method provides the flexibility required to work with almost any identity provider and security framework. With Spring Security, you can define a FilterChain for your application programmatically. The following section is from the business-application-service/src/main/java/com/company/service/DefaultWebSecurityConfig.java file from a Spring Boot business application service file created using the Maven archetype command. For information, see Section 3.1, "Creating a Spring Boot business application from Maven archetypes" . @Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override (1) protected void configure(HttpSecurity http) throws Exception { http .cors().and() .csrf().disable() (2) .authorizeRequests() (3) .antMatchers("/rest/*").authenticated().and() .httpBasic().and() (4) .headers().frameOptions().disable(); (5) } (1) Overrides the default configure(HttpSecurity http) method and defines a custom FilterChain using the Spring HttpClient fluent API/DSL (2) Disables common exploit filters for CORS and CSRF tokens for local testing (3) Requires authentication for any requests made to the pattern 'rest/*' but no roles are defined (4) Allows basic authentication through the authorization header, for example header 'Authorization: Basic dGVzdF91c2VyOnBhc3N3b3Jk' (5) Removes the 'X-Frame-Options' header from request/response This configuration allows any authenticated user to execute the KIE API. Because the default implementation is not integrated into any external identity provider, users are defined in memory, in the same DefaultWebSecurityConfg class. The following section shows the users that are provided when you create a Red Hat Decision Manager Spring Boot business application: @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("user").password("user").roles("kie-server"); auth.inMemoryAuthentication().withUser("wbadmin").password("wbadmin").roles("admin"); auth.inMemoryAuthentication().withUser("kieserver").password("kieserver1!").roles("kie-server"); } 4.1. Using Spring Security to authenticate with authorization By default, anyone with a user name and password valid for the Red Hat Decision Manager Spring Boot application can access the application without requiring a role. Spring Security authentication and authorization are derived from the HTTPSecurity filter chain configuration. To protect the REST API from users that do not have a specific role mapping, use the Spring Security .authorizeRequests() method to match the URLs that you want to authorize. Prerequisites You have a Red Hat Decision Manager Spring Boot application. Procedure In the directory that contains your Red Hat Decision Manager Spring Boot application, open the business-application-service/src/main/java/com/company/service/DefaultWebSecurityConfig.java file in a text editor or IDE. To authorize requests for access by an authenticated user only if they have a specific role, edit the .antMatchers("/rest/*").authenticated().and() line in one of the following ways: To authorize for a single role, edit the antMatchers method as shown in the following example, where <role> is the role that that the user must have for access: @Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/**").hasRole("<role>") .anyRequest().authenticated() .and().httpBasic() .and().headers().frameOptions().disable(); } ... To authorize a user that has one of a range of roles, edit the antMatchers method as shown in the following example, where <role> and <role1> are each roles the user can have for access: @Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/**").hasAnyRole("<role>", "<role1") .anyRequest().authenticated() .and().httpBasic() .and().headers().frameOptions().disable(); } ... The authorizeRequests method requires authorization of requests for a specific expression. All requests must be successfully authenticated. Authentication is performed using HTTP basic authentication. If an authenticated user tries to access a resource that is protected for a role that they do not have, the user receives an HTTP 403 (Forbidden) error. 4.2. Disabling Spring Security in a Red Hat Decision Manager business application You can configure Spring Security in a Red Hat Decision Manager business application to provide the security context without authentication. Prerequisites You have a Red Hat Decision Manager Spring Boot application. Procedure In the directory that contains your Red Hat Decision Manager Spring Boot application, open the business-application-service/src/main/java/com/company/service/DefaultWebSecurityConfig.java file in a text editor or integrated development environment (IDE). Edit the .antMatchers method as shown in the following example: @Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/*") .permitAll() .and().headers().frameOptions().disable(); } The PermitAll method allows any and all requests for the specified URL pattern. Note Because no security context is passed in the HttpServletRequest , Spring creates an AnonymousAuthenticationToken and populates the SecurityContext with the anonymousUser user with no designated roles other than the ROLE_ANONYMOUS role. The user will not have access to many of the features of the application, for example they will be unable to assign actions to group assigned tasks. 4.3. Using Spring Security with preauthenication If you disable Spring Security authentication by using the PermitAll method, any user can log in to the application, but users will have limited access and functionality. However, you can preauthenticate a user, for example a designated service account, so a group of users can use the same login but have all of the permissions that they require. That way, you do not need to create credentials for each user. The easiest way to implement preauthentication is to create a custom filter servlet and add it before the security FilterChain in the DefaultWebSecurityConfig class. This way, you can inject a customized, profile-based security context, control its contents, and keep it simple. Prerequisites You have a Red Hat Decision Manager Spring Boot application and you have disabled Spring Security as Section 4.2, "Disabling Spring Security in a Red Hat Decision Manager business application" . Procedure Create the following class that extends the AnonymousAuthenticationFilter class: import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; public class <CLASS_NAME> extends AnonymousAuthenticationFilter { private static final Logger log = LoggerFactory.getLogger(<CLASS_NAME>.class); public AnonymousAuthFilter() { super("PROXY_AUTH_FILTER"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req)); log.info("SecurityContextHolder pre-auth user: {}", SecurityContextHolder.getContext()); if (log.isDebugEnabled()) { log.debug("Populated SecurityContextHolder with authenticated user: {}", SecurityContextHolder.getContext().getAuthentication()); } chain.doFilter(req, res); } @Override protected Authentication createAuthentication(final HttpServletRequest request) throws AuthenticationException { log.info("<ANONYMOUS_USER>"); List<? extends GrantedAuthority> authorities = Collections .unmodifiableList(Arrays.asList(new SimpleGrantedAuthority("<ROLE>") )); return new AnonymousAuthenticationToken("ANONYMOUS", "<ANONYMOUS_USER>", authorities); } } Replace the following variables: Replace <CLASS_NAME> with a name for this class, for example AnonymousAuthFilter . Replace <ANONYMOUS_USER> with a user ID, for example Service_Group . Replace <ROLE> with the role that has the privileges that you want to give to <ANONYMOUS_USER> . If you want to give <ANONYMOUS_USER> more than one role, add additional roles as shown in the following example: .unmodifiableList(Arrays.asList(new SimpleGrantedAuthority("<ROLE>") , new SimpleGrantedAuthority("<ROLE2>") Add .anonymous().authenticationFilter(new <CLASS_NAME>()).and() to the business-application-service/src/main/java/com/company/service/DefaultWebSecurityConfig.java file, where <CLASS_NAME> is the name of the class that you created: @Override protected void configure(HttpSecurity http) throws Exception { http .anonymous().authenticationFilter(new <CLASS_NAME>()).and() // Override anonymousUser .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/*").permitAll() .and().headers().frameOptions().disable(); } 4.4. Configuring the business application with Red Hat Single Sign-On Most organizations provide user and group details through single sign-on (SSO) tokens. You can use Red Hat Single Sign-On (RHSSO) to enable single sign-on between your services and to have a central place to configure and manage your users and roles. Prerequisites You have a Spring Boot business application. Procedure Download and install RHSSO. For instructions, see the Red Hat Single Sign-On Getting Started Guide . Configure RHSSO: Either use the default master realm or create a new realm. A realm manages a set of users, credentials, roles, and groups. A user belongs to and logs into a realm. Realms are isolated from one another and can only manage and authenticate the users that they control. Create the springboot-app client and set the AccessType to public. Set a valid redirect URI and web origin according to your local setup, as shown in the following example: Valid redirect URIs: http://localhost:8090/* Web origin: http://localhost:8090 Create realm roles that are used in the application. Create users that are used in the application and assign roles to them. Add the following element and property to the Spring Boot project pom.xml file, where <KEYCLOAK_VERSION> is the version of Keycloak that you are using: <properties> <version.org.keycloak><KEYCLOAK_VERSION></version.org.keycloak> </properties> Add the following dependencies to the Spring Boot project pom.xml file: <dependencyManagement> <dependencies> <dependency> <groupId>org.keycloak.bom</groupId> <artifactId>keycloak-adapter-bom</artifactId> <version>USD{version.org.keycloak}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> .... <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-boot-starter</artifactId> </dependency> In your Spring Boot project directory, open the business-application-service/src/main/resources/application.properties file and add the following lines: Modify the business-application-service/src/main/java/com/company/service/DefaultWebSecurityConfig.java file to ensure that Spring Security works correctly with RHSSO: import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; @Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http .csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); SimpleAuthorityMapper mapper = new SimpleAuthorityMapper(); mapper.setPrefix(""); keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(mapper); auth.authenticationProvider(keycloakAuthenticationProvider); } @Bean public KeycloakConfigResolver KeycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } }

Commands:
@Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override (1) protected void configure(HttpSecurity http) throws Exception { http .cors().and() .csrf().disable() (2) .authorizeRequests() (3) .antMatchers("/rest/*").authenticated().and() .httpBasic().and() (4) .headers().frameOptions().disable(); (5) }
@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("user").password("user").roles("kie-server"); auth.inMemoryAuthentication().withUser("wbadmin").password("wbadmin").roles("admin"); auth.inMemoryAuthentication().withUser("kieserver").password("kieserver1!").roles("kie-server"); }
@Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/**").hasRole("<role>") .anyRequest().authenticated() .and().httpBasic() .and().headers().frameOptions().disable(); }
@Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/**").hasAnyRole("<role>", "<role1") .anyRequest().authenticated() .and().httpBasic() .and().headers().frameOptions().disable(); }
@Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/*") .permitAll() .and().headers().frameOptions().disable(); }
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; public class <CLASS_NAME> extends AnonymousAuthenticationFilter { private static final Logger log = LoggerFactory.getLogger(<CLASS_NAME>.class); public AnonymousAuthFilter() { super("PROXY_AUTH_FILTER"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req)); log.info("SecurityContextHolder pre-auth user: {}", SecurityContextHolder.getContext()); if (log.isDebugEnabled()) { log.debug("Populated SecurityContextHolder with authenticated user: {}", SecurityContextHolder.getContext().getAuthentication()); } chain.doFilter(req, res); } @Override protected Authentication createAuthentication(final HttpServletRequest request) throws AuthenticationException { log.info("<ANONYMOUS_USER>"); List<? extends GrantedAuthority> authorities = Collections .unmodifiableList(Arrays.asList(new SimpleGrantedAuthority("<ROLE>") )); return new AnonymousAuthenticationToken("ANONYMOUS", "<ANONYMOUS_USER>", authorities); } }
.unmodifiableList(Arrays.asList(new SimpleGrantedAuthority("<ROLE>") , new SimpleGrantedAuthority("<ROLE2>")
@Override protected void configure(HttpSecurity http) throws Exception { http .anonymous().authenticationFilter(new <CLASS_NAME>()).and() // Override anonymousUser .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/*").permitAll() .and().headers().frameOptions().disable(); }
<properties> <version.org.keycloak><KEYCLOAK_VERSION></version.org.keycloak> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>org.keycloak.bom</groupId> <artifactId>keycloak-adapter-bom</artifactId> <version>USD{version.org.keycloak}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> . <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-boot-starter</artifactId> </dependency>
keycloak security setup keycloak.auth-server-url=http://localhost:8100/auth keycloak.realm=master keycloak.resource=springboot-app keycloak.public-client=true keycloak.principal-attribute=preferred_username keycloak.enable-basic-auth=true
import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; @Configuration("kieServerSecurity") @EnableWebSecurity public class DefaultWebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http .csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); SimpleAuthorityMapper mapper = new SimpleAuthorityMapper(); mapper.setPrefix(""); keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(mapper); auth.authenticationProvider(keycloakAuthenticationProvider); } @Bean public KeycloakConfigResolver KeycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } }