Adding JWT MtM Authentication
Overview
Atoti Limits includes native support for JSON Web Tokens.
We can utilize this support to send MtM requests using the Authorization: Jwt ...
header.
note
This support is provided by Atoti Limits, but we will show how it is implemented nonetheless to provide a sample extension mechanism.
Using the default support
To use the native support, specify the following properties in both Atoti Limits and your connected server:
limits:
autoconfiguration:
use-jwt-machine-to-machine-auth: true // this tells Atoti Limits and the connected server to use JWT MtM authentication
service-principal: admin // this tells Atoti Limits and the connected server the principal user designated to invoke MtM request
warning
You must use the kebab-case use-jwt-machine-to-machine-auth
and not the camelCase
useJwtMachineToMachineAuth
or else Spring will not correctly pick up your property.
How it works
As described in Adding Custom Machine-to-Machine (MtM) Authentication, we need to add some custom implementations to our code.
1. Adding a custom ILimitsRestClientProvider
This example is an implementation of ILimitsRestClientProvider
, which uses JWT authentication:
@Getter
public class LimitsJwtRestClientProvider implements ILimitsRestClientProvider {
protected final RestClient limitsRestClient;
// We inject any services we need into our provider
public LimitsJwtRestClientProvider(
IJwtService jwtService, // this is used to get or generate the JWT
UserDetailsService userDetailsService, // this is used to get the authorities of the service user
LimitsConnectionConfigurationProperties limitsConnectionConfigurationProperties
// we use this class to pull in properties, but you could use any property extraction mechanism
) {
// we build the client to be used for requests
this.limitsRestClient =
buildRestClient(jwtService, userDetailsService, limitsConnectionConfigurationProperties);
}
private RestClient buildRestClient(
IJwtService jwtService,
UserDetailsService userDetailsService,
LimitsConnectionConfigurationProperties limitsConnectionConfigurationProperties) {
String servicePrincipal =
limitsConnectionConfigurationProperties.getAutoconfiguration().getServicePrincipal();
if (servicePrincipal == null) {
throw new LimitsRuntimeException(
"You must specify the `limits.autoconfiguration.service-principal` property if you are using JWT machine-to-machine authentication!");
}
Collection<String> authorities =
userDetailsService.loadUserByUsername(servicePrincipal).getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
String jwt = jwtService.getToken(servicePrincipal, authorities);
// here is where we set the `Authorization: Jwt ...` header
return RestClient.builder().defaultHeader(AUTHORIZATION, "Jwt " + jwt).build();
}
}
2. Adding a custom ILimitsRestClientBuilderProvider
This example is an implementation of ILimitsRestClientBuilderProvider
, which uses JWT authentication. It
is almost identical to above, except we return a builder instead of a fully constructed client.
@Getter
public class JwtRestClientBuilderProvider implements ILimitsRestClientBuilderProvider {
protected final RestClient.Builder limitsRestClientBuilder;
public JwtRestClientBuilderProvider(
IJwtService jwtService,
UserDetailsService userDetailsService,
LimitsAutoconfigurationProperties limitsAutoconfigurationProperties) {
this.limitsRestClientBuilder =
buildRestClient(jwtService, userDetailsService, limitsAutoconfigurationProperties);
}
private RestClient.Builder buildRestClient(
IJwtService jwtService,
UserDetailsService userDetailsService,
LimitsAutoconfigurationProperties limitsAutoconfigurationProperties) {
String servicePrincipal = limitsAutoconfigurationProperties.getServicePrincipal();
if (servicePrincipal == null) {
throw new LimitsRuntimeException(
"You must specify the `limits.autoconfiguration.service-principal` property if you are using JWT machine-to-machine authentication!");
}
Collection<String> authorities =
userDetailsService.loadUserByUsername(servicePrincipal).getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
String jwt = jwtService.getToken(servicePrincipal, authorities);
return RestClient.builder().defaultHeader(AUTHORIZATION, "Jwt " + jwt);
}
}