如何使用 Spring Security 保护 Vaadin 流应用程序
Posted
技术标签:
【中文标题】如何使用 Spring Security 保护 Vaadin 流应用程序【英文标题】:How to secure Vaadin flow application with Spring Security 【发布时间】:2019-04-02 15:47:25 【问题描述】:我正在尝试将 vaadin 10 与 spring security 集成(使用 vaadin 提供的 spring 项目库),但我对它们之间的交互方式感到困惑。如果我直接在浏览器中输入受保护的 url(在本例中为“/about”),则会显示登录页面。如果我通过单击 UI 中的链接访问相同的 URL,即使我未通过身份验证,该页面也会显示。所以我猜 Vaadin 没有通过 Spring Security 的过滤器链,但是我如何保护 UI 内的资源,以及如何在 vaadin 和 spring 之间共享经过身份验证的用户?我应该两次实施安全性吗?可用的文档似乎没有涵盖这一点,互联网上的每个链接都有 Vaadin 7-8 的示例,我从未使用过,而且似乎与 10+ 的工作方式不同。
有没有人知道这方面的任何资源,或者你能告诉我所有这些是如何协同工作的,这样我就可以知道我在做什么?
这是我的安全配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
private static final String[] ALLOWED_GET_URLS =
"/",
//"/about",
"/login/**",
"/frontend/**",
"/VAADIN/**",
"/favicon.ico"
;
private static final String[] ALLOWED_POST_URLS =
"/"
;
//@formatter:off
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf()
.disable()
.authorizeRequests()
.mvcMatchers(HttpMethod.GET, ALLOWED_GET_URLS)
.permitAll()
.mvcMatchers(HttpMethod.POST, ALLOWED_POST_URLS)
.permitAll()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll();
//@formatter:on
【问题讨论】:
【参考方案1】:使用 Vaadin Flow (12.0.2)、Spring Boot Starter (2.0.2.RELEASE) 和 Spring Boot Security,基本上,我发现使用以下方式基于角色/权限进行授权;
基于路由/上下文的角色/权限管理
Spring 安全性 (HttpSecurity) Vaadin API(BeforeEnterListener 和 Route/Navigation API)业务部门角色/权限管理
代码里面使用HttpServletRequest.isUserInRole方法让我们从一个简单的 Spring Security 配置示例开始;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf().disable() // CSRF is handled by Vaadin: https://vaadin.com/framework/security
.exceptionHandling().accessDeniedPage("/accessDenied")
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and().logout().logoutSuccessUrl("/")
.and()
.authorizeRequests()
// allow Vaadin URLs and the login URL without authentication
.regexMatchers("/frontend/.*", "/VAADIN/.*", "/login.*", "/accessDenied").permitAll()
.regexMatchers(HttpMethod.POST, "/\\?v-r=.*").permitAll()
// deny any other URL until authenticated
.antMatchers("/**").fullyAuthenticated()
/*
Note that anonymous authentication is enabled by default, therefore;
SecurityContextHolder.getContext().getAuthentication().isAuthenticated() always will return true.
Look at LoginView.beforeEnter method.
more info: https://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/anonymous.html
*/
;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth
.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password("$2a$10$obstjyWMAVfsNoKisfyCjO/DNfO9OoMOKNt5a6GRlVS7XNUzYuUbO").roles("ADMIN");// user and pass: admin
/**
* Expose the AuthenticationManager (to be used in LoginView)
* @return
* @throws Exception
*/
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
如您所见,我还没有在我的任何路由视图(使用@Route 注释)上指定任何基于角色的权限。我要做的是,如果我有一个路由视图,我将在构建它(路由视图)时注册一个 BeforeEnterListener,并在那里检查所需的角色/权限。
以下是在导航到 admin-utils 视图之前检查用户是否具有 ADMIN 角色的示例;
@Route(value = "admin-utils")
public class AdminUtilsView extends VerticalLayout
@Autowired
private HttpServletRequest req;
...
AdminUtilsView()
...
UI.getCurrent().addBeforeEnterListener(new BeforeEnterListener()
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent)
if (beforeEnterEvent.getNavigationTarget() != DeniedAccessView.class && // This is to avoid a
// loop if DeniedAccessView is the target
!req.isUserInRole("ADMIN"))
beforeEnterEvent.rerouteTo(DeniedAccessView.class);
);
如果用户没有 ADMIN 角色,(s)他将被路由到 Spring Security 配置中已经允许所有人使用的 DeniedAccessView。
@Route(value = "accessDenied")
public class DeniedAccessView
extends VerticalLayout
DeniedAccessView()
FormLayout formLayout = new FormLayout();
formLayout.add(new Label("Access denied!"));
add(formLayout);
在上面的示例(AdminUtilsView)中,您还可以通过自动装配 HttpServletRequest 在 Vaadin 代码中看到 HttpServletRequest.isUserInRole() 的用例。
总结:如果你的view有Route,先使用BeforeEnterListener授权请求,否则使用Spring Security 用于休息服务等的匹配器(例如 regexMatchers 或 antMatchers)。
注意: 将 Vaadin Route 和 Spring Security 匹配器规则一起用于同一规则可能有点扭曲,我不建议这样做(它会导致 Vaadin 中的一些内部循环;例如,想象一下我们有一个使用 /view 路由的视图和一个具有所需角色的 /view 条目在 Spring Security 中。如果用户缺少这样的角色并且(s)他被路由/导航到这样的页面(使用 Vaadin 路由 API),Vaadin 尝试以打开与路由关联的视图,而 Spring 安全性会因缺少角色而避免这种情况)。
另外,我认为,在将用户重新路由或导航到不同的视图/上下文之前,使用 Vaadin 流导航 API 是一种很好的做法,即检查所需的角色/权限。
此外,为了有一个在 Vaadin 中使用 AuthenticationManager 的示例,我们可以有一个基于 Vaadin 的 LoginView,类似于:
@Route(value = "login")
public class LoginView
extends FlexLayout implements BeforeEnterObserver
private final Label label;
private final TextField userNameTextField;
private final PasswordField passwordField;
/**
* AuthenticationManager is already exposed in WebSecurityConfig
*/
@Autowired
private AuthenticationManager authManager;
@Autowired
private HttpServletRequest req;
LoginView()
label = new Label("Please login...");
userNameTextField = new TextField();
userNameTextField.setPlaceholder("Username");
UiUtils.makeFirstInputTextAutoFocus(Collections.singletonList(userNameTextField));
passwordField = new PasswordField();
passwordField.setPlaceholder("Password");
passwordField.addKeyDownListener(Key.ENTER, (ComponentEventListener<KeyDownEvent>) keyDownEvent -> authenticateAndNavigate());
Button submitButton = new Button("Login");
submitButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
authenticateAndNavigate();
);
FormLayout formLayout = new FormLayout();
formLayout.add(label, userNameTextField, passwordField, submitButton);
add(formLayout);
// center the form
setAlignItems(Alignment.CENTER);
this.getElement().getStyle().set("height", "100%");
this.getElement().getStyle().set("justify-content", "center");
private void authenticateAndNavigate()
/*
Set an authenticated user in Spring Security and Spring MVC
spring-security
*/
UsernamePasswordAuthenticationToken authReq
= new UsernamePasswordAuthenticationToken(userNameTextField.getValue(), passwordField.getValue());
try
// Set authentication
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
/*
Navigate to the requested page:
This is to redirect a user back to the originally requested URL – after they log in as we are not using
Spring's AuthenticationSuccessHandler.
*/
HttpSession session = req.getSession(false);
DefaultSavedRequest savedRequest = (DefaultSavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
String requestedURI = savedRequest != null ? savedRequest.getRequestURI() : Application.APP_URL;
this.getUI().ifPresent(ui -> ui.navigate(StringUtils.removeStart(requestedURI, "/")));
catch (BadCredentialsException e)
label.setText("Invalid username or password. Please try again.");
/**
* This is to redirect user to the main URL context if (s)he has already logged in and tries to open /login
*
* @param beforeEnterEvent
*/
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent)
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//Anonymous Authentication is enabled in our Spring Security conf
if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken))
//https://vaadin.com/docs/flow/routing/tutorial-routing-lifecycle.html
beforeEnterEvent.rerouteTo("");
最后,这里是可以从菜单或按钮调用的注销方法:
/**
* log out the current user using Spring security and Vaadin session management
*/
void requestLogout()
//https://***.com/a/5727444/1572286
SecurityContextHolder.clearContext();
req.getSession(false).invalidate();
// And this is similar to how logout is handled in Vaadin 8:
// https://vaadin.com/docs/v8/framework/articles/HandlingLogout.html
UI.getCurrent().getSession().close();
UI.getCurrent().getPage().reload();// to redirect user to the login page
您可以通过查看以下示例继续使用 Spring UserDetailsService 完成角色管理并创建 PasswordEncoder bean:
https://github.com/igor-baiborodine/vaadin-demo-bakery-app/blob/master/src/main/java/com/kiroule/vaadin/bakeryapp/app/security/SecurityConfiguration.java https://github.com/igor-baiborodine/vaadin-demo-bakery-app/blob/master/src/main/java/com/kiroule/vaadin/bakeryapp/app/security/UserDetailsServiceImpl.java https://www.baeldung.com/role-and-privilege-for-spring-security-registration【讨论】:
哇,很好的答案。不能更直接地使用 Spring Security 太糟糕了,但这是一个很好的直接解决方案。稍后我会在自己的测试项目中尝试一下,但它看起来像是我正在寻找的答案 非常好的答案对我有用。我发现这个教程看起来也很有帮助vaadin.com/tutorials/securing-your-app-with-spring-security以上是关于如何使用 Spring Security 保护 Vaadin 流应用程序的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Spring Security 保护 Vaadin 流应用程序
如何使用 Spring Security 保护 REST Web 服务
如何使用 Spring Security Oauth 保护 Struts2 Rest 服务
如何使用 Spring Security OAuth2 和 MITREID Connect Introspect 保护资源?