Issue
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:
- href="https://vaadin.com/learn/tutorials/securing-your-app-with-spring-security" rel="nofollow noreferrer" title="The definitive guide to Spring Security with Vaadin 14+">Securing your app with Spring Security
- Router Exception Handling
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:
- First instantiate
AccessDeniedException
and then pass it to overloaded method inBeforeEvent
:public void rerouteToError(Exception exception, String customMessage)
which should skip creating exception object by reflection - Create dedicated
ErrorView
and use methodpublic void rerouteTo(Class<? extends Component> routeTargetType, RouteParameters parameters)
ofBeforeEvent
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;
}
}
Solution
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.
Answered By - Leif Åstrand
Answer Checked By - Marie Seifert (JavaFixing Admin)