I have a checkForBankId annotation
@Target(value = { ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(value=RetentionPolicy.RUNTIME)
public @interface checkForBankId {
boolean ignore() default false;
}
And i have a BankIdSecurityService
public class BankIdSecurityService {
/*
* This is declarative client to call the
* InternalAPIs to get all details about the user;
*/
@Inject
UserRepositoryClient client;
static final boolean AUTHORIZED = true;
static final boolean UnAUTHENTICATED = false;
Flowable<Boolean> checkAuthentication(HttpRequest<?> request) {
if (routeMatch instanceof MethodBasedRouteMatch) {
MethodBasedRouteMatch<?, ?> methodBasedRouteMatch =
(MethodBasedRouteMatch<?, ?>) routeMatch;
if (methodBasedRouteMatch.hasAnnotation(checkForBankId.class)) {
String userName = getUserName(request).get();
if(!request.getParameter().contains("bankId")) {
return Flowable.just(UNAUTHORIZED);
}
return this.client.getUser(userName).map(userDetails -> {
String bankId = request.getParameter().get("bankId");
BankAccount[] bankAccounts = userDetails.getPayload().getBankAccounts();
boolean bankIdFound = false;
for(BankAcconut bankAccout: bankAccounts) {
if(bankAccount.getId().equals(bankId)) {
bankIdFound = true;
break;
}
}
return bankIdFound ? AUTHENTICATED : UNAUTHORIZED;
});
}
}
return Flowable.just(AUTHENTICATED);
}
Optional<String> getUserName(HttpRequest<?> request) {
// get jwt from request header
then parse the jwt and extract
the username from it and return;
}
}
then i have Filter which executes once per request
@Filter(Filter.MATCH_ALL_PATTERN)
@AllArgsConstructor
@Slf4j
public class BankIdSecurityFilter extends OncePerRequestHttpServerFilter {
private final BankIdSecurityService bankIdSecurityService;
@Override
protected Publisher<MutableHttpResponse<?>> doFilterOnce(
HttpRequest<?> request, ServerFilterChain chain
) {
log.info("request route: {}", request);
return this.bankIdSecurityService
.checkAuthentication(request).switchMap(authResult -> {
log.info("authentication result: {}", authResult);
return authResult ? // if authResult is true then proceed as usual
chain.proceed(request) :
Publishers.just(HttpResponse.status(HttpStatus.BAD_REQUEST)
.body("This bankId doesn't belond to this user.")
); // return bad request
});
}
@Override
public int getOrder() {
return ServerFilterPhase.SECURITY.after();
}
}
Inside controller we apply the @CheckForBankId where we want to filter the request
@CheckForBankId
@GET("get-bank-account?{bankId}")
Single<BankAccount> getBankAccount(String bankId) {
return someService.getBankAccount(bankId);
}
The filter is working fine. The thing which is missing is that it gets executed before microanut security so, the token which is coming in header is unauthenticated and from some cases it can lead to some problems like what if an expired token passed to an header and my parser will parse the token and will return the user from it but it makes no sense as the token is already invalid. It should have already been reject before coming to the filter..so, like this filter to get executed after micronaut security. I tried overridding the getOrder method but it didn't work :(
You didn't specify which Micronaut version you're working with, but the answer is that the behavior is a bug. It should work as expected in Micronaut 2.5.4