我有以下的路线和意见:
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配置了Security,一切都很好。例如,当我试图访问一个安全页面时,系统会自动将我重定向到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.
DashboardView定义:
@Scope("prototype")
@Route(value = "dashboard", layout = MainLayout.class)
@PageTitle("Dashboard")
@PermitAll
public class DashboardView extends VerticalLayout {
我做错了什么,怎么解决呢?
更新
我在com.vaadin
上启用了com.vaadin
,现在可能会看到以下消息:
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登录页面以便对用户进行身份验证?
发布于 2022-07-25 21:34:00
经过几个小时的调试,我似乎找到了解决这个问题的方法:
我创建了自己的类(扩展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合作。
https://stackoverflow.com/questions/73109782
复制相似问题