Используя Vaadin Flow (12.0.2), Spring Boot Starter (2.0.2.RELEASE) и Spring Boot Security, я обнаружил, что авторизация на основе роли / полномочий осуществляется следующими способами:
Управление ролями / полномочиями на основе маршрута / контекста
- Безопасность Spring (HttpSecurity)
- API Vaadin (BeforeEnterListener и 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 перед переходом в представление admin-utils;
@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, он будет перенаправлен на DeniedAccessView, что уже разрешено для всех в конфигурации Spring Security.
@Route(value = "accessDenied")
public class DeniedAccessView
extends VerticalLayout {
DeniedAccessView() {
FormLayout formLayout = new FormLayout();
formLayout.add(new Label("Access denied!"));
add(formLayout);
}
}
В приведенном выше примере (AdminUtilsView) вы также можете увидеть вариант использования HttpServletRequest.isUserInRole () в коде Vaadin, автоматически подключив HttpServletRequest.
РЕЗЮМЕ: Если ваше представление имеет маршрут, используйте BeforeEnterListener для авторизации запроса в первую очередь, в противном случае используйте сопоставители Spring Security (например, regexMatchers или antMatchers) для служб отдыха и т. д.
ПРИМЕЧАНИЕ. Использование правил сопоставления Vaadin Route и Spring Security вместе для одного и того же правила может быть немного искаженным, и я не предлагаю этого (это вызывает некоторые внутренние петли в Vaadin; например, представьте, что у нас есть представление, направленное с помощью / view, и запись в Spring Security для / view с требуемой ролью. Если у пользователя отсутствует такая роль и он перенаправляется / перемещается на такую страницу (с использованием API маршрутизации Vaadin), Vaadin пытается открыть представление, связанное с маршрутом, в то время как безопасность Spring позволяет избежать этого из-за отсутствия роли).
Кроме того, я думаю, что использование API навигации по потоку Vaadin перед перенаправлением или переходом пользователя в другое представление / контекст было бы хорошей практикой, чтобы проверить наличие требуемой роли / полномочий.
Более того, чтобы иметь пример использования AuthenticationManager в Vaadin, у нас может быть LoginView на основе Vaadin, похожий на:
@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://stackoverflow.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 и создать bean-компонент PasswordEncoder, просмотрев следующие примеры:
person
Youness
schedule
05.01.2019