4

I started implementing authentication and authorization for our applications written in Spring Boot (2.2.6.RELEASE) and Vaadin 14 LTS (14.6.1).

I have followed those resources:

I have code for checking whether logged-in user has access rights to specified resources implemented in beforeEnter method. The problem is with invocation of event.rerouteToError(AccessDeniedException.class);. It tries to create an instance of the specified exception with reflection but fails because it does not contain public no-arg constructor.

private void beforeEnter(final BeforeEnterEvent event) {
    if (!AuthView.class.equals(event.getNavigationTarget()) && !AuthUtils.isUserLoggedIn()) {
        event.rerouteTo(AuthView.class);
    }

    if (!AuthUtils.isAccessGranted(event.getNavigationTarget())) {
        event.rerouteToError(AccessDeniedException.class);
    }
}
java.lang.IllegalArgumentException: Unable to create an instance of 'org.springframework.security.access.AccessDeniedException'. Make sure the class has a public no-arg constructor.
    at com.vaadin.flow.internal.ReflectTools.createProxyInstance(ReflectTools.java:519)
    at com.vaadin.flow.internal.ReflectTools.createInstance(ReflectTools.java:451)
    at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:720)
    at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:704)

What can be the best solution for that case? I am thinking about two possible solutions:

  1. First instantiate AccessDeniedException and then pass it to overloaded method in BeforeEvent: public void rerouteToError(Exception exception, String customMessage) which should skip creating exception object by reflection
  2. Create dedicated ErrorView and use method public void rerouteTo(Class<? extends Component> routeTargetType, RouteParameters parameters) of BeforeEvent

I decided to follow Leif Åstrand's answer. I created custom AccessDeniedException and appropriate error handler. Here is my implementation. Maybe it will be helpful for someone.

public class AccessDeniedException extends RuntimeException {
    private final int code;

    public AccessDeniedException() {
        super("common.error.403.details");
        this.code = HttpServletResponse.SC_FORBIDDEN;
    }

    public int getCode() {
        return code;
    }

}
@Tag(Tag.DIV)
@CssImport(value = "./styles/access-denied-view.css")
@CssImport(value = "./styles/access-denied-box.css", themeFor = "vaadin-details")
public class AccessDeniedExceptionHandler extends VerticalLayout implements HasErrorParameter<AccessDeniedException> {

    private final Details details;

    public AccessDeniedExceptionHandler() {
        setWidthFull();
        setHeight("100vh");
        setPadding(false);
        setDefaultHorizontalComponentAlignment(Alignment.CENTER);
        setJustifyContentMode(JustifyContentMode.CENTER);
        setClassName(ComponentConstants.ACCESS_DENIED_VIEW);

        this.details = new Details();
        this.details.setClassName(ComponentConstants.ACCESS_DENIED_BOX);
        this.details.addThemeVariants(DetailsVariant.REVERSE, DetailsVariant.FILLED);
        this.details.setOpened(true);

        add(this.details);
    }

    @Override
    public final int setErrorParameter(final BeforeEnterEvent event, final ErrorParameter<AccessDeniedException> parameter) {
        final int code = parameter.getException().getCode();

        this.details.setSummaryText(getTranslation("common.error.403.header", code));
        this.details.setContent(new Text(getTranslation(parameter.getException().getMessage())));

        return code;
    }

}

2 Answers2

3

I would recommend creating a custom exception type instead of reusing AccessDeniedException from Spring. In that way, you don't have to deal with the required error message at all.

Leif Åstrand
  • 4,265
  • 8
  • 14
0

As you mentioned in your first solution, you could do:

event.rerouteToError(new AccessDeniedException("Navigation target not permitted"), "");
  • or maybe also specify the customMessage if you want. If you see the implementation of the rerouteToError(Class) method, it just passes empty customMessage and creates the Exception - which you could do manually and that's completely acceptable. I recommend this solution.

Another solution could be to subclass AccessDeniedException and use that with reflection:

public class RouteAccessDeniedException extends AccessDeniedException {
    public RouteAccessDeniedException() {
        super("Navigation target not permitted");
    }
}

I don't recommend this solution.

Hawk
  • 1,219
  • 5
  • 14