Using micronaut to create rest end-point which is protected by using micronaut-jwt security
@Post
@IRequirement(resourceName = ClaimType.TAG_PRODUCT, permission = {ClaimValue.TAG_OWNER,ClaimValue.TAG_CREATOR,ClaimValue.TAG_MAINTAINER })
Mono<MutableHttpResponse<?>> post(@Body @Valid ProductModel model){
LOG.info(String.format("Creating new product"));
return _iServiceBus.<ProductModel, Mono<ProductModel>>send(model).flatMap(item -> {
if (item != null) {
try {
return Mono.just(HttpResponse.created(item, new URI(String.format("/%s", item.id()))));
} catch (URISyntaxException e) {
return Mono.error(new GlobalException(e));
}
} else
return Mono.just(HttpResponse.serverError());
}
).onErrorMap(throwable -> {
throw new GlobalException(throwable);
});}
Security model
@Inherited
public @interface IRequirement {
String resourceName();
String[] permission();
}
Security rules
@Singleton
public class AuthorityHandler implements SecurityRule {
@Override
public Publisher<SecurityRuleResult> check(HttpRequest<?> request, @Nullable RouteMatch<?> routeMatch, @Nullable Authentication authentication) {
if (routeMatch instanceof MethodBasedRouteMatch methodBasedRouteMatch) {
if (methodBasedRouteMatch.hasAnnotation(IRequirement.class)) {
AnnotationValue<IRequirement> requiredPermissionAnnotation = methodBasedRouteMatch.getAnnotation(IRequirement.class);
Optional<String> resourceIdName = requiredPermissionAnnotation.stringValue( "resourceName");
String[] permissions = requiredPermissionAnnotation.stringValues("permission");
if (permissions.length > 0 && resourceIdName.isPresent() && authentication != null) {
Map<String, Object> identityClaims = authentication.getAttributes();
if (Arrays.stream(permissions).anyMatch(element -> identityClaims.containsValue(element)))
return Mono.just(SecurityRuleResult.ALLOWED);
else
return Mono.just(SecurityRuleResult.REJECTED);
}
}
}
return Mono.just(SecurityRuleResult.UNKNOWN);
}
}
The above end-point is protected as claims owner
,creator
and maintainer
I am using Identity server 4 (https://github.com/IdentityServer/IdentityServer4) to manage the identity and when the user logins in it contain the following claims in the access token
{
"nbf": 1641548846,
"exp": 1641549046,
"iss": "https://localhost:5001",
"client_id": "Falcon_Identity_Server",
"sub": "673533cc-7c0b-40f3-80ac-222696df385d",
"auth_time": 1641548840,
"idp": "local",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "673533cc-7c0b-40f3-80ac-222696df385d",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "admin@local.com",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "admin@local.com",
"AspNet.Identity.SecurityStamp": "812aa4cb-b9f1-48ac-9e39-1f0dceb6f1c4",
"identityserver": "owner",
"fb_product": "owner",
"fb_order": "owner",
"fb_payment": "owner",
"jti": "179683F5CCA01EB925B45B1CA6379080",
"sid": "F1D9F917D34178EDAF7A5FD24948F89D",
"iat": 1641548846,
"scope": [
"openid",
"profile",
"email"
],
"amr": [
"pwd"
]
}
The above end point is looking for those attributes
"identityserver": "owner",
"fb_product": "owner",
"fb_order": "owner",
"fb_payment": "owner",
For running an real application, it is working fine. I am trying to write the test case using micronaut httpclient. But not sure how can I test using micronaut test framework
Something I tried
@Test
@DisplayName("Should create a JWT token")
void shouldCreateAJwtToken() {
BearerAccessRefreshToken bearerAccessRefreshToken = null;
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("sherlock", "password");
HttpRequest request = HttpRequest.POST("/login", creds);
HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken.class);
bearerAccessRefreshToken = rsp.body();
}
It creates a token but doesn't contain all the validation required for the above access token.
How do we do rest endpoint unit testing that is protected by JWT token in micronaut
Authentication provider
@Singleton
@Requires(env = Environment.TEST)
public record AuthenticationProviderUserPasswordFixture() implements AuthenticationProvider {
@Override
public Publisher<AuthenticationResponse> authenticate(HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
return Flux.create(emitter -> {
if (authenticationRequest.getIdentity().equals("sherlock") && authenticationRequest.getSecret().equals("password")) {
emitter.next(AuthenticationResponse.success((String) authenticationRequest.getIdentity(), List.of("roles1", "roles2")));
emitter.complete();
} else {
emitter.error(AuthenticationResponse.exception());
}
}, FluxSink.OverflowStrategy.ERROR);
}
}