如何在 Vaadin 14 LTS 中正确处理拒绝访问

问题描述

我开始为我们用 Spring Boot (2.2.6.RELEASE)Vaadin 14 LTS (14.6.1) 编写的应用程序实施身份验证和授权。

我已关注这些资源:

我有代码用于检查登录用户是否对在 beforeEnter 方法中实现的指定资源具有访问权限。问题在于调用 event.reroutetoError(AccessDeniedException.class);。它尝试使用反射创建指定异常的实例但失败,因为它不包含公共无参数构造函数

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)

对于这种情况,最好的解决方案是什么?我正在考虑两种可能的解决方案:

  1. 首先实例化 AccessDeniedException,然后将其传递给 BeforeEvent 中的重载方法 public void reroutetoError(Exception exception,String customMessage) 应该跳过通过反射创建异常对象
  2. 创建专用的 ErrorView 并使用 public void rerouteto(Class<? extends Component> routetargettype,RouteParameters parameters)方法 BeforeEvent

我决定遵循 Leif Åstrand's 的回答。我创建了自定义 AccessDeniedException 和适当的错误处理程序。这是我的实现。也许它会对某人有所帮助。

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;
    }

}

解决方法

我建议创建自定义异常类型,而不是重用 Spring 中的 AccessDeniedException。这样,您根本不必处理所需的错误消息。

,

正如您在第一个解决方案中提到的,您可以:

event.rerouteToError(new AccessDeniedException("Navigation target not permitted"),"");
  • 或者如果需要也可以指定 customMessage。如果您看到 rerouteToError(Class) 方法的实现,它只会传递空 customMessage 并创建异常 - 您可以手动执行此操作,这是完全可以接受的。 我推荐此解决方案。

另一种解决方案可能是继承 AccessDeniedException 并将其与反射一起使用:

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

推荐这种解决方案。