spring-security Vaadin 23主页,可匿名访问

flvtvl50  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(176)

我定义了以下路线和视图:

private void createDrawer() {

        Tabs tabs = new Tabs();
        tabs.add(
                createTab(VaadinIcon.HOME, "Home", HomeView.class),
                createTab(VaadinIcon.DASHBOARD, "Dashboard", DashboardView.class),
                createTab(VaadinIcon.INBOX, "Inbox", InboxView.class),
                createTab(VaadinIcon.WORKPLACE, "Jobs", JobsView.class),
                createTab(VaadinIcon.USER, "Candidates", CandidatesView.class),
                createTab(VaadinIcon.GAVEL, "Administration", AdministrationView.class)
        );
        tabs.setOrientation(Tabs.Orientation.VERTICAL);

        addToDrawer(tabs);
    }

    private Tab createTab(VaadinIcon viewIcon, String viewName, Class<? extends Component> navigationTarget) {
        Icon icon = viewIcon.create();
        icon.getStyle()
                .set("box-sizing", "border-box")
                .set("margin-inline-end", "var(--lumo-space-m)")
                .set("margin-inline-start", "var(--lumo-space-xs)")
                .set("padding", "var(--lumo-space-xs)");

        RouterLink link = new RouterLink();
        link.add(icon, new Span(viewName));
        link.setRoute(navigationTarget);
        link.setTabIndex(-1);

        return new Tab(link);
    }

每个视图都定义了以下注解,例如

@Scope("prototype")
@Component
@Route(value = "", layout = MainLayout.class)
@PageTitle("Home")
@PermitAll
public class HomeView extends VerticalLayout {

由于@PermitAll的原因,它需要来自用户的身份验证。我用Keycloak 18配置了Spring Security,一切都工作正常。当我尝试访问一个安全页面时,例如http://localhost:8080/-系统会自动将我重定向到Keycloak登录页面。
现在,我想允许匿名访问我的应用程序的主页(HomeView)。为此,我将@PermitAll更改为@AnonymousAllowed

@Scope("prototype")
@Component
@Route(value = "", layout = MainLayout.class)
@PageTitle("Home")
@AnonymousAllowed
public class HomeView extends VerticalLayout {

现在,我可以使用匿名用户访问http://localhost:8080/(HomeView)。这很好。但是,当我单击另一个选项卡链接(例如DashboardView)时,它会显示以下消息:

Could not navigate to 'dashboard'

Available routes:

    <root>
    administration
    candidates
    dashboard
    inbox
    jobs
    list
    login

This detailed message is only shown when running in development mode. Instead of that I expect that the system will automaticall redirect me to Keycloak login page.

Jmeter 板视图定义:

@Scope("prototype")
@Route(value = "dashboard", layout = MainLayout.class)
@PageTitle("Dashboard")
@PermitAll
public class DashboardView extends VerticalLayout {

我做错了什么,该如何改正?

已更新

我在com.vaadin上启用了DEBUG,现在可能会看到以下消息:

2022-07-25 18:35:25.564 DEBUG 18984 --- [nio-8080-exec-8] c.v.flow.spring.SpringViewAccessChecker  : Checking access for view com.decisionwanted.ui.views.dashboard.DashboardView
2022-07-25 18:35:25.564 DEBUG 18984 --- [nio-8080-exec-8] c.v.flow.spring.SpringViewAccessChecker  : Denied access to view com.decisionwanted.ui.views.dashboard.DashboardView
2022-07-25 18:35:25.567 DEBUG 18984 --- [nio-8080-exec-8] c.v.flow.spring.SpringViewAccessChecker  : Checking access for view com.vaadin.flow.router.RouteNotFoundError
2022-07-25 18:35:25.568 DEBUG 18984 --- [nio-8080-exec-8] c.v.flow.spring.SpringViewAccessChecker  : Allowed access to view com.vaadin.flow.router.RouteNotFoundError
2022-07-25 18:35:25.580 DEBUG 18984 --- [nio-8080-exec-8] c.vaadin.flow.router.RouteNotFoundError  : Route is not found

com.vaadin.flow.router.NotFoundException: null
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na]
    at com.vaadin.flow.internal.ReflectTools.createProxyInstance(ReflectTools.java:484) ~[flow-server-23.1.3.jar:23.1.3]
    at com.vaadin.flow.internal.ReflectTools.createInstance(ReflectTools.java:452) ~[flow-server-23.1.3.jar:23.1.3]
    at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:766) ~[flow-server-23.1.3.jar:23.1.3]
    at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:750) ~[flow-server-23.1.3.jar:23.1.3]
    at com.vaadin.flow.server.auth.ViewAccessChecker.beforeEnter(ViewAccessChecker.java:192) ~[flow-server-23.1.3.jar:23.1.3]

这是我的SecurityConfig:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    final ClientRegistrationRepository clientRegistrationRepository;
    final GrantedAuthoritiesMapper authoritiesMapper;

    SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository,
                          GrantedAuthoritiesMapper authoritiesMapper) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authoritiesMapper = authoritiesMapper;
        SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName());
    }

    @Bean
    public SessionRepository sessionRepository() {
        return new SessionRepository();
    }

    @Bean
    public ServletListenerRegistrationBean<SessionRepositoryListener> sessionRepositoryListener() {
        var bean = new ServletListenerRegistrationBean<SessionRepositoryListener>();
        bean.setListener(new SessionRepositoryListener(sessionRepository()));
        return bean;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                // Enable OAuth2 login
                .oauth2Login(oauth2Login ->
                        oauth2Login
                                .clientRegistrationRepository(clientRegistrationRepository)
                                .userInfoEndpoint(userInfoEndpoint ->
                                        userInfoEndpoint
                                                // Use a custom authorities mapper to get the roles from the identity provider into the Authentication token
                                                .userAuthoritiesMapper(authoritiesMapper)
                                )
                                // Use a Vaadin aware authentication success handler
                                .successHandler(new VaadinSavedRequestAwareAuthenticationSuccessHandler())
                )
                // Configure logout
                .logout(logout ->
                        logout
                                // Enable OIDC logout (requires that we use the 'openid' scope when authenticating)
                                .logoutSuccessHandler(logoutSuccessHandler())
                                // When CSRF is enabled, the logout URL normally requires a POST request with the CSRF
                                // token attached. This makes it difficult to perform a logout from within a Vaadin
                                // application (since Vaadin uses its own CSRF tokens). By changing the logout endpoint
                                // to accept GET requests, we can redirect to the logout URL from within Vaadin.
                                .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
                );
    }

    private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler() {
        var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
        return logoutSuccessHandler;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        // Don't apply security rules on our static pages
        web.ignoring().antMatchers("/session-expired");
    }

    @Bean
    public PolicyFactory htmlSanitizer() {
        // This is the policy we will be using to sanitize HTML input
        return Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS);
    }

}

为什么Vaadin不把我重定向到Keycloak登录页面,而是失败了?在这种情况下,如何配置重定向到Keycloak登录页面以验证用户?

puruo6ea

puruo6ea1#

经过几个小时的调试,我似乎找到了解决此问题的方法:
我创建了自己的类(扩展的SpringViewAccessChecker):

public class KeycloakSpringViewAccessChecker extends SpringViewAccessChecker {

    private AccessAnnotationChecker accessAnnotationChecker;
    private String keycloakAuthorizationUrl;

    public KeycloakSpringViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker, String keycloakAuthorizationUrl) {
        super(null);
        this.accessAnnotationChecker = accessAnnotationChecker;
        this.keycloakAuthorizationUrl = keycloakAuthorizationUrl;
    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
        Class<?> targetView = beforeEnterEvent.getNavigationTarget();
        VaadinRequest request = VaadinRequest.getCurrent();

        Principal principal = getPrincipal(request);
        Function<String, Boolean> rolesChecker = getRolesChecker(request);

        boolean hasAccess = accessAnnotationChecker.hasAccess(targetView, principal, rolesChecker);

        if (!hasAccess) {

            if (principal == null) {
                HttpSession session = (request instanceof VaadinServletRequest) ? ((VaadinServletRequest) request).getSession() : null;
                if (session != null) {
                    session.setAttribute(SESSION_STORED_REDIRECT, "/" + beforeEnterEvent.getLocation().getPathWithQueryParameters());
                }
            }

            beforeEnterEvent.getUI().getPage().setLocation(keycloakAuthorizationUrl);
        }
    }

}

用我的实现覆盖了Bean:

@Bean
@Primary
public SpringViewAccessChecker springViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker) {
    return new KeycloakSpringViewAccessChecker(accessAnnotationChecker, "/oauth2/authorization/keycloak");
}

看起来它现在可以按预期使用Keycloak了

相关问题