This page will introduce more detail about Customer Search in back-end and language details of kotlin and the use of micronaut framework.

  • Related technologies

  1. https://kotlinlang.org/

  2. https://docs.micronaut.io/index.html

  • Customer Search controller

You can see the code in backoffice-manager.

Almost all controller functions have @BankUserOnly. It verifies whether you have permission.

This annotation is defined in https://github.com/SafiBank/SaFiMono/blob/main/common/iam-auth-lib/src/main/kotlin/ph/safibank/common/auth/bankuser/BankUserOnly.kt .

@Target(ElementType.METHOD, ElementType.TYPE)     (1)
@Retention(RetentionPolicy.RUNTIME)               (2)
@Inheritedannotation class BankUserOnly

(1) indicates only method and type can use this annotation.

(2) means annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.

This class is really important. We can see the class implemented SecurityRule. And SecurityRule extends Ordered.

BankUserOnlyAnnotationRule is customize SecurityRule, you should override getOrder function. Write custom auth check logic in check function.

And another important argument is BankUserAuthentication:

@BankUserOnly
fun functionName(bankUser: BankUserAuthentication)

BankUserAuthentication is defined in the code:

/**
 * Represents authenticated BankUser
 *
 * It returns [BANK_USER_ROLE] for all bank users, that can be used with
 * [io.micronaut.security.annotation.Secured] or checked with [BankUserOnly]
 * annotation.
 *
 * It can be injected in to handlers using [BankUserAuthenticationArgumentBinder]:
 * ```
 * @Controller
 * class ExampleController {
 *     @Get("/")
 *     @AllowBankUser
 *     fun bankUserEndpoint(bankUser BankUserAuthentication) {
 *        ...
 *     }
 * }
 * ```
 *
 *  @see [BankUserOnly]
 */
data class BankUserAuthentication(
    /**
     * ID of Bank User in Okta
     */
    val uid: String,

    /**
     * Display name as defined in Okta mapping
     */
    val displayName: String,
) : Authentication {

    override fun getName(): String = displayName

    override fun getRoles(): List<String> = listOf(BANK_USER_ROLE)

    override fun getAttributes(): Map<String, Any> = hashMapOf()
}

So controller function parameter bankUser: BankUserAuthentication is handled by BankUserAuthenticationArgumentBinder.

And BankUserAuthenticationArgumentBinder is defined in the code:

@Singletonclass BankUserAuthenticationArgumentBinder :
    AbstractPrincipalArgumentBinder<BankUserAuthentication>(BankUserAuthentication::class.java)

Let’s see AbstractPrincipalArgumentBinder, which is defined in io.micronaut.security.authentication .

/**
 * Binds the authentication object to a route argument.
 *
 * @param <A> the {@link Principal} type
 * @author Burt Beckwith
 * @since 3.2
 */
public abstract class AbstractPrincipalArgumentBinder<A extends Principal> implements TypedRequestArgumentBinder<A> {

    private final Class<A> authenticationClass;
    private final Argument<A> argumentType;

    protected AbstractPrincipalArgumentBinder(Class<A> authenticationClass) {
        this.authenticationClass = authenticationClass;
        argumentType = Argument.of(authenticationClass);
    }

    @SuppressWarnings("unchecked")
    @Override
    public BindingResult<A> bind(ArgumentConversionContext<A> context,
                                 HttpRequest<?> source) {

        if (!source.getAttributes().contains(SecurityFilter.KEY)) {
            return BindingResult.UNSATISFIED;
        }

        final Optional<A> existing = source.getUserPrincipal(authenticationClass);
        return existing.isPresent() ? (() -> existing) : BindingResult.EMPTY;
    }

    @Override
    public Argument<A> argumentType() {
        return argumentType;
    }
}

This class implements TypedRequestArgumentBinder(A TypeArgumentBinder that binds from an HttpRequest).

So if you want to write custom argument binder to implement it.

The function will bind return BankUserAuthentication , which has been stored in request.

To see how source.getUserPrincipal works.

// The user principal stored within the request. 
default @NonNull <T extends Principal> Optional<T> getUserPrincipal(Class<T> principalType) {
    return getAttribute(HttpAttributes.PRINCIPAL, principalType);}
public enum HttpAttributes implements CharSequence {

    // Attribute used to store the {@link java.security.Principal}.
    PRINCIPAL("micronaut.AUTHENTICATION"),
    ...
}
public interface HttpRequest<B> extends HttpMessage<B> {
...
    @Override
    default HttpRequest<B> setAttribute(CharSequence name, Object value) {
        return (HttpRequest<B>) HttpMessage.super.setAttribute(name, value);
    }
}

When http request is sent to backoffice-manager controller. BankUserAuthentication can be set to request. Then bankUser: BankUserAuthentication can be injected into controller the parameter of function.