Using Spring Security 5 to integrate with OAuth2 secured RESTFull API without login and servlet context.

Howto config a Spring Security OAuth2 client that is capable of operating outside of the context of a HttpServletRequest,
e.g. in a scheduled/background thread and/or in the service-tier.

With the new Spring Security 5, there are a lot of examples about howto configure a client to access service like, Facebook, GitHub and many others with the standard OAuth2.
But today I found diffulties to get documentations about howto access OAuth2 secured RESTFull API with a RestTemplate client, without login and servlet context. And so I had to debug Spring Security framework to figure out the right configuration.

My env is as a follow:

Java 8
Spring Boot 2.4.1
Spring Security 5.4.2
Spring Web 5.3.2

The pom dependencies are:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

The api that I have to access are secured with OAuth2 with GRANT_TYPE=PASSWORD and client auth method equals to POST.
Every call to the Api must contains an Authorization header with the access token of type Bearer.
To obtain the access token, we need a token uri, a client id and the client username/password. All the information must be provided by the resource server.

What we need is a RestTemplateConfig. The file of this example can be found here.

We’re going to see the config step by step. First of all we need a ClientRegistrationRepository

    Builder b = ClientRegistration.withRegistrationId(registrationId);
    b.authorizationGrantType(AuthorizationGrantType.PASSWORD);
    b.clientAuthenticationMethod(ClientAuthenticationMethod.POST);
    b.tokenUri(tokenUri);
    b.clientId(clientId);
    ClientRegistrationRepository clients = new InMemoryClientRegistrationRepository(b.build());

the tokenUri end the clientId must be provided by the resource server.

Then we need the service:

    OAuth2AuthorizedClientService service = new InMemoryOAuth2AuthorizedClientService(clients);

the authorized client provider:

    OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder().password().refreshToken().build();

and the manager:

    AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
            clients, service);
    manager.setAuthorizedClientProvider(authorizedClientProvider);
    manager.setContextAttributesMapper(new Function<OAuth2AuthorizeRequest, Map<String, Object>>() {

        @Override
        public Map<String, Object> apply(OAuth2AuthorizeRequest authorizeRequest) {
            Map<String, Object> contextAttributes = new HashMap<>();
            String scope = authorizeRequest.getAttribute(OAuth2ParameterNames.SCOPE);
            if (StringUtils.hasText(scope)) {
                contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,
                        StringUtils.delimitedListToStringArray(scope, " "));
            }

            String username = authorizeRequest.getAttribute(OAuth2ParameterNames.USERNAME);
            if (StringUtils.hasText(username)) {
                contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
            }

            String password = authorizeRequest.getAttribute(OAuth2ParameterNames.PASSWORD);
            if (StringUtils.hasText(password)) {
                contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
            }

            return contextAttributes;
        }

    });

then we can add an interceptors to the RestTemplate:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder, OAuth2AuthorizedClientManager manager) {
    RestTemplate restTemplate = builder.build();
    restTemplate.getInterceptors().add(new BearerTokenInterceptor(manager, username, password, registrationId));

    return restTemplate;
}


public class BearerTokenInterceptor implements ClientHttpRequestInterceptor {
    private final Logger LOG = LoggerFactory.getLogger(BearerTokenInterceptor.class);

    private OAuth2AuthorizedClientManager manager;
    private String username;
    private String password;
    private String registrationId;

    public BearerTokenInterceptor(OAuth2AuthorizedClientManager manager, String username, String password, String registrationId) {
        this.manager = manager;
        this.username = username; 
        this.password = password;
        this.registrationId = registrationId; 
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution)
            throws IOException {
        String accessToken = null;
        OAuth2AuthorizedClient client = manager.authorize(OAuth2AuthorizeRequest.withClientRegistrationId(registrationId)
                .attribute(OAuth2ParameterNames.USERNAME, username)
                .attribute(OAuth2ParameterNames.PASSWORD, password)
                .principal(principal).build()); 
        accessToken = client.getAccessToken() != null ? client.getAccessToken().getTokenValue() : null;
        if (accessToken != null) {
            LOG.debug("Request body: {}", new String(bytes, StandardCharsets.UTF_8));
            request.getHeaders().add("Authorization", "Bearer " + accessToken);
            return execution.execute(request, bytes);
        } else {
            throw new IllegalStateException("Can't access the API without an access token");
        }
    }

}

before every call, the manager try to authorize the client with the username and password provided by the resource server. If the authentication is successful, the server return a json like this:

{
“access_token”: “hjdhjYU00jjTYYT….”,
“token_type”: “Bearer”,
“expires_in”: “3600”,
“refresh_token”: “hdshTT55jhds…”,
}

Since the server support refresh token, we have configured the authorizedClientProvider to manage the refresh token in case the access token provided is expired.

That’s all!

Advertisement

Data Masking with JPA and Spring Security

The protection of sensitive data is an increasingly popular topic in IT applications

The protection of sensitive data is an increasingly popular topic in IT applications. Also in our case, a customer asked us, on an already existing web application, to implement a data masking solution that is dynamic and based on security profiles.

The application is developed in Java, with Spring MVC for the management of the Model View Controller, JPA for data access and Spring Security for the management of security profiles.

There are two approaches in literature: SDM (Static Data Masking) and DDM (Dynamic Data Masking).

SDM

SDM plans to clone the current database by masking sensitive data. Specific inquiry applications that provide data masking can read from the cloned database.

Advantages:

  • performance of data access at runtime

Disadvantages:

  • data read can be not updated (update takes place via batch and, depending on the mode, the update can last from minutes to hours)
  • not ideal for a role-based / field-based security scenario

DDM

DDM plans to mask data when it is read at runtime.

Advantages:

  • real data reading,
  • ideal for a role-based / field-based security scenario

Disadvantages:

  • read / write overhead performance
  • possible unmusk algorithms to avoid data corruption (to prevent the masked data from persisting on the DB)

Given the customer’s requests, the DDM technique is the one that best suits a dynamic scenario based on security profiles.

At this point another choice had to be made because for DDM there are two approaches:

JPA Rewriting

In the literature we talk about SQL Rewriting, in our specific case JPA rewriting, JPA being our data access layer. The data is masked in a PostLoad or PostUpdate annotated method of a JPA Entity Listener, that means in the persistent layer.

Advantages:

  • punctual masking of the data in the load phase from the DB
  • easy data-masking mapping

Disadvantages:

  • masking depending on the data type (for example a string can be masked with ‘***’, or with ‘###’, a number with ‘000’ or ‘999’, a date with ’99 / 99 / 9999 ‘, etc etc …)
  • difficulty in the Look & Feel for rendering the view if the data is masked (each view should declare the masking … re-enter in the case of View rewriting below)
  • unmask algorithms that use the user session to store unmasked data. JPA makes shering of objects loaded by DB, so it is not said that an object loaded by an inquiry function is not then used for an update function. In this case the masked data would be persisted on DB, that means data corruption
  • complex make the masking dependent on the function (use of the user session for function-masking mapping)
  • complex use of the user session (see above for unmask and function-masking mapping)

View Rewriting

The data is masked in the presentation layer, typically in jsp pages.

Advantages:

  • homogeneous masking (does not depend on the type of data, everything can be masked for example with ‘***’)
  • it is not required unmusk phase
  • easy rendering for a look & feel (each view declares whether or not it wants masking)
  • easy to make it dependent function (each function declare whether or not it wants masking)

Disadvantages:

  • not punctual masking (all the views must mask … the tags reused by the view simplify, but not completely)
  • difficult data-masking mapping (each view must declare the data)

We chose to adopt the View Rewriting, because analyzing the effort (which in this article omits because not relevant), it was, more or less, similar between the two approaches, while the risk of data corruption and out of memory exceptions of user session are absent. Moreover the View Rewriting solution is much more customizable for what concerns the Look & Feel

To implement the solution we need the following things in detail:

  • a generic editor to enable or not a field for masking
  • a masking class that performs data masking based on security profiles
  • to modify all existing views to use the masking class above

Let’s see in detail

Role-based security mapping

We use a role-based security mapping based on Spring Security (already present in the application). For any data that you want to mask, a role is created made like this:

ROLE_MASK_DOMAIN-NAME_FIELD-NAME

for example, if I want to mask the tax code field of the people table, since the field is mapped via JPA in Person.taxCode, the role will be

ROLE_MASK_PERSON_TAXCODE

The mapping editing is managed dynamically with a special GUI function. We used the existing Domanin Editor function, a generic domain editor that for all domain classes it allows the modification of all the fields mapped to the database.
We have added a new editing form for managing data-masking mapping.
The form will contain all the fields of the chosen domain class. For each field you can choose (with a special checkbox) whether or not to enable the relative masking. When saving, the function performs the following steps:

  • look in the Authorities table if the role ROLE_MASK_DOMAIN-NAME_FIELD-NAME exists. If it does not exist it creates (the opposite if the field must be disabled)

For mapping with profiles (Spring Security Groups) are used the already present Spring Security functions implemented in the appropriate View of the application.

Masking class

Creation of a class that receives as input the data to be masked and its name (for example, Person.taxCode).
The class looks for (with the methods that provide Spring Secutiry) if the current user’s profile is associated with the corresponding field (ROLE_MASK_PERSON_TAXCODE / Person.taxCode). If exists, the class mask the data and returns it to the view.

Change Views

The functions that provide for masking the data are typically those of inquiry. In our case it helps us the fact that we have adopted tags in the presentation layer so all the shows and lists use a display.tagx tag and a table.tagx tag. We need to change these two tags to make them use the masking class.
The longest work concerns modifies all jsps that use the two tags, which must declare the name of the field they are viewing.

Finally we have also modified the search filters to make sure that if the filter provides the search for a field that must be masked, the filter is disabled.
For example, if the filter requires a search for tax code, the filter must use the masking class to know at runtime if the profile expects to mask this data.
If so, the filter is disabled.

Conclusions

View Rewriting with role based security is the best solution for the following reasons:

  • effort slightly greater than the JPA Rewriting solution but more or less similar
  • use of spring security to map the data to be masked to the profile
  • greater custom in terms of look & feel
  • absence of data corruption risk
  • absence of user session out of memory risk

Howto migrate LDAP users to Spring Security JDBC

Howto migrate LDAP users to Spring Security JDBC

Lately we are redoing a web portal whose users who have access to the private area of ​​the application are registered through LDAP.
The only request made by the contractor is that the transition to the new portal must be transparent to registered users. This translates into the fact that users do not have to change the password the first time they log in to the new portal. Passwords are stored in the LDAP repository with SSHA (Salted SHA) encoding.
Our application uses Spring Security to manage security and access to the reserved area. Spring Security supports various types of authentication including LDAP itself. As a first idea we thought to use the same LDAP repository already present. After analyzing this solution in detail we have thought not to take this solution for various reasons.
The first is to map the roles related to the permissions of the old application to the roles of our application (feasible but not very nice from the point of functional view). Furthermore, having to maintain two separate servers, an LDAP and a DBMS, when it is possible to have only one DBMS server, is not a good thing from the point of management costs.
So we thought about using the classic Spring Security JDBC authentication. The users will be migrated through a batch that will load the unloading LDAP users (download, for example, done in the csv format) to the JDBC tables. To ensure that password encryption remains the same, just configure Spring Security to use the LdapShaPasswordEncoder class. To do this you need to define the following bean in WebMvcConfiguration:

@Bean
public LdapShaPasswordEncoder passwordEncoderLDAP () {
return new LdapShaPasswordEncoder ();
}

end using it in the AuthenticationManagerBuilder defined in WebSecurityConfiguration like this:

@Autowired
private LdapShaPasswordEncoder ldapPasswordEncoder;

@Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService (webJdbcUserDetailsManager).passwordEncoder (ldapPasswordEncoder);
}

in this way the password coding will be the same used by LDAP and for users the transition to the new portal will be transparent.