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

Converting fixed fields text record to JSON

Using a REST Service for converting fixed fields text record to json with Fixedfid java library.

Converting fixed fields text record to JSON can be realized in many ways. A solution can be the using of a REST Service combined with the Fixefid java library.

The environment is as a follows:

  • Java 8
  • Spring Boot 2.3.4.RELEASE
  • Spring Web
  • Fixefid 1.1.0
  • Spring Doc Openapi 1.5.0

The Fixefid java library permits to define a fixed fields text record with Java Bean or Java Enum. In this case the definition by Java Bean can be used to annotate a resource representation class of a REST Service.

For instance, we want converting a customer record like this one:

String record = "0000000000000000001Paul                                              Robinson                                          ";

to a json object like this one:

{
"id": 1,        
"firstName": "Paul",        
"lastName": "Robinson"    
}

To model the customer representation, we can create a resource representation class:

@FixefidRecord
public class Customer {
	@FixefidField(fieldLen = 19, fieldOrdinal = 0, fieldType = FieldType.N)
	private Long id;
@FixefidField(fieldLen = 50, fieldOrdinal = 1, fieldType = FieldType.AN)
private String firstName;

@FixefidField(fieldLen = 50, fieldOrdinal = 2, fieldType = FieldType.AN)
private String lastName;

protected Customer() {
}

public Customer(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

@Override
public String toString() {
    return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
}

public Long getId() {
    return id;
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}
}

The resource representation class is annotated with the Fixefid annotations. Then we can create the record request:

public class RecordRequest {
	private Long requestId;
	private String record;

public String getRecord() {
    return record;
}

public void setRecord(String record) {
    this.record = record;
}

public Long getRequestId() {
    return requestId;
}

public void setRequestId(Long requestId) {
    this.requestId = requestId;
}
}

and the Customer response:

public class CustomerResponse {
	private Long requestId;
	private Long responseId;
	private Customer customer;

public CustomerResponse(Long requestId, Long responseId, Customer customer) {
    this.requestId = requestId;
    this.responseId = responseId;
    this.customer = customer;
}

public Long getRequestId() {
    return requestId;
}
public void setRequestId(Long requestId) {
    this.requestId = requestId;
}
public Long getResponseId() {
    return responseId;
}
public void setResponseId(Long responseId) {
    this.responseId = responseId;
}
public Customer getCustomer() {
    return customer;
}
public void setCustomer(Customer customer) {
    this.customer = customer;
}
}

last, the rest controller:

@RestController
public class CustomerController {
	private final AtomicLong counter = new AtomicLong();

        @PostMapping(path = "/recordtocustomer", consumes = 
        "application/json", produces = "application/json")
        public CustomerResponse recordToCustomer(@RequestBody RecordRequest 
           request) {
        Customer customer = new Customer(null, null);
        new BeanRecord(customer, request.getRecord());
        return new CustomerResponse(request.getRequestId(), 
        counter.incrementAndGet(), customer);
     }
}

With Postman we can test the service:

Here the project of the example on github.

Howto deal with fixed fields text record and JPA Entity

If the persistance layer is realized with JPA, we can mapping record fields directly to Entity fields

Fixefid is a java library wich permits to define a fixed fields text record with Java Bean or Java Enum. Often a text record must be retrieved from data persisted on a database. Or a text record must be persisted on a database.

The solution is to define a mapping from the record’s fields with the persistence model. If the persistance layer is realized with JPA Entities, the mapping can be done directly to the JPA Entity with the Fixefid annotations. Infact a JPA Entity is a POJO, that’s a Java Bean. And so, we can annotate the JPA Entity with the Fixefid annotations to realize the mapping, without the need to create two models, one for the record and another one for the JPA Entity.

The environment is as a follows:

  • Java 8
  • Spring Boot 2.3.4.RELEASE
  • Spring Data JPA
  • Fixefid 1.1.0
  • H2 Database

For example we can have a Customer bean like this one:

@Entity
@FixefidRecord
public class Customer {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@FixefidField(fieldLen = 19, fieldOrdinal = 0, fieldType = FieldType.N)
	private Long id;
	
	@FixefidField(fieldLen = 50, fieldOrdinal = 1, fieldType = FieldType.AN)
	private String firstName;
	
	@FixefidField(fieldLen = 50, fieldOrdinal = 2, fieldType = FieldType.AN)
	private String lastName;

	protected Customer() {
	}

	public Customer(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
	}

	public Long getId() {
		return id;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}
}

The Customer above is annotated with Entity and FixefidRecord. The fields are annotated with FixefidField and other JPA annotations. To obtain the record from the database:

Customer customer = repository.findById(1L);
String record = new BeanRecord(customer).toString();

To save the record to the database:

String newRecord = "0000000000000000001Paul                                              Robinson                                          ";
Customer newCustomer = new Customer();
new BeanRecord(newCustomer, newRecord);
repository.save(newCustomer);

I made a video tutorial where the example above is explained in the detail way.

Here the project of the example on github.